60

我有一个 ListView,里面有一些可聚焦的组件(主要是EditTexts)。是的,我知道这不是完全推荐的,但总的来说,几乎所有东西都运行良好,并且焦点集中在它必须去的地方(我必须进行一些调整)。无论如何,我的问题是,当用手指滚动列表然后在显示 IME 键盘时突然使用轨迹球时,会出现奇怪的竞争条件。某些东西必须越界并被回收,此时该offsetRectBetweenParentAndChild()方法必须启动并抛出IllegalArgumentException.

问题是这个异常被抛出在我可以插入 try/catch 的任何块之外(据我所知)。所以这个问题有两个有效的解决方案,要么:

  1. 有人知道为什么会抛出这个异常以及如何阻止它发生
  2. 有人知道如何将 try/catch 块放在至少可以让我的应用程序存活的地方。据我所知,问题在于焦点,所以它绝对不应该杀死我的应用程序(这就是它正在做的事情)。我尝试覆盖ViewGroup's 方法,但这两种offset*方法被标记为最终方法。

堆栈跟踪:

08-17 18:23:09.825: ERROR/AndroidRuntime(1608): FATAL EXCEPTION: main
08-17 18:23:09.825: ERROR/AndroidRuntime(1608): java.lang.IllegalArgumentException: parameter must be a descendant of this view
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewGroup.offsetRectBetweenParentAndChild(ViewGroup.java:2633)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewGroup.offsetDescendantRectToMyCoords(ViewGroup.java:2570)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewRoot.scrollToRectOrFocus(ViewRoot.java:1624)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewRoot.draw(ViewRoot.java:1357)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewRoot.performTraversals(ViewRoot.java:1258)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1859)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.os.Handler.dispatchMessage(Handler.java:99)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.os.Looper.loop(Looper.java:130)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.app.ActivityThread.main(ActivityThread.java:3683)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at java.lang.reflect.Method.invokeNative(Native Method)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at java.lang.reflect.Method.invoke(Method.java:507)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at dalvik.system.NativeStart.main(Native Method)
4

16 回答 16

38

虽然布鲁斯的回答确实解决了问题,但它以一种非常残酷的方式解决了问题,这会损害用户体验,因为一旦我们进行滚动,它将清除每个视图的焦点。

它处理问题的症状,但不能解决实际原因。

如何重现问题:

您的 EditText 具有焦点并打开了键盘,然后您滚动到 EditText 离开屏幕的位置,并且它没有被回收到现在显示的新 EditText 。

我们先来了解一下为什么会出现这个问题:

众所周知,ListView 会回收其视图并再次使用它们,但有时它不需要立即使用已离开屏幕的视图,因此它会保留它以供将来使用,并且因为它不再需要显示它将分离它导致 view.mParent 为空。但是键盘需要知道如何将输入传递给它,并且它通过选择聚焦视图或 EditText 来做到这一点。

所以问题是我们有一个 EditText 有焦点,但突然没有父级,所以我们得到一个“参数必须是这个视图的后代”错误。是有道理的。

通过使用滚动侦听器,我们会导致更多问题。

解决方案:

我们需要监听一个事件,它会告诉我们视图何时进入侧堆并且不再附加,幸运的是 ListView 公开了这个事件。

listView.setRecyclerListener(new AbsListView.RecyclerListener() {
        @Override
        public void onMovedToScrapHeap(View view) {
            if ( view.hasFocus()){
                view.clearFocus(); //we can put it inside the second if as well, but it makes sense to do it to all scraped views
                //Optional: also hide keyboard in that case
                if ( view instanceof EditText) {
                    InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
                }
            }
        }
    });
于 2016-11-17T16:05:54.727 回答
33

很遗憾地告诉你,我发现我之前的回答并不是解决这个问题的最完美方法。

所以我试试这个:
将一个 ScrollListener 附加到你的 Activity 中,当 listView 开始滚动时,清除当前焦点。

protected class MyScrollListener implements OnScrollListener {

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
            // do nothing 
        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if (SCROLL_STATE_TOUCH_SCROLL == scrollState) {
                View currentFocus = getCurrentFocus();
                if (currentFocus != null) {
                    currentFocus.clearFocus();
                }
            }
        }

    }
于 2012-10-15T09:02:37.577 回答
25

try this

 @Override
public View getView(int position, View convertView, ViewGroup parent) {
    //abandon current focus
    View currentFocus = ((Activity)mContext).getCurrentFocus();
    if (currentFocus != null) {
        currentFocus.clearFocus();
    }

    // other code
}

EDIT:

See also: Better Solution

于 2012-10-15T03:05:14.227 回答
12

对于它的价值(或任何偶然发现的人),我已经放弃了此 Activity 的 ListView 方法。除了随机崩溃之外,如果不设置windowSoftInputMode="adjustPan"打开一堆其他蠕虫罐,几乎不可能正确地获得焦点行为。相反,我只是选择了一个“简单”的 ScrollView,而且效果很好。

于 2012-04-24T17:19:11.863 回答
9

我用布鲁斯的答案稍作调整。

我需要adjustResize在我的活动中而不是adjustpan但是当我尝试它时,错误再次发生。
我替换ScrollView<android.support.v4.widget.NestedScrollView,现在可以正常工作了。希望这对某人有帮助!

于 2016-10-30T11:07:49.533 回答
8

我有最简单但不是很好的解决方案。只需扩展 NestedScrollView 并覆盖 onSizeChanged 方法,添加一个 try catch 块。

public class FixFocusErrorNestedScrollView extends NestedScrollView {

    public FixFocusErrorNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        try {
            super.onSizeChanged(w, h, oldw, oldh);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

就我而言,我有两个层视图,顶层是 listView,底部是 NestedScrollView。切换图层时发生错误。ListeView 项目(按钮)将获得焦点。

所以我不能让按钮失去焦点。那么最好的解决方案就是扩展 NestedScrollView。

于 2018-08-22T09:11:59.947 回答
7

我遇到了同样的问题并找到了这个解决方案 - 在我清除焦点并隐藏强制键盘中OnGroupCollapseListener/OnGroupExpandListener。也不要忘记为您的活动设置:OnScrollListenerExpandableListViewmanifestwindowSoftInputMode="adjustPan"

    expListView.setOnGroupCollapseListener(new OnGroupCollapseListener() {

        @Override
        public void onGroupCollapse(int groupPosition) {
            InputMethodManager inputManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            if (getWindow().getCurrentFocus() != null) {
                inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
                getCurrentFocus().clearFocus();
            }
        }
    });

    expListView.setOnGroupExpandListener(new OnGroupExpandListener() {

        @Override
        public void onGroupExpand(int groupPosition) {
            InputMethodManager inputManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            if (getWindow().getCurrentFocus() != null) {
                inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
                getCurrentFocus().clearFocus();
            }
        }
    });

    expListView.setOnScrollListener(new OnScrollListener() {

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            InputMethodManager inputManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            if (getCurrentFocus() != null) {
                inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
                getCurrentFocus().clearFocus();
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}

    });

我不知道确切OnGroupExpandListener是否需要,它可能没用。

于 2014-05-28T08:53:59.927 回答
4

我也遇到了这个问题,validcat 的解决方案对我有用,但我不得不打电话给getWindow().getCurrentFocus().clearFocus().

于 2015-09-08T21:37:55.253 回答
4

EditText我在使用时遇到了同样的问题Recyclerview。经过一番努力并尝试了不同的选项后,我发现在打开键盘时删除该行后会产生此问题。我通过强制关闭键盘并更改notifyItemRemoved(position)notifyDataSetChanged().

于 2018-02-20T11:45:27.337 回答
4

在可扩展列表视图的情况下,如果您的子项目具有编辑文本,那么您需要在可扩展列表视图的后代之前更改可聚焦性

expandableListView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
于 2017-04-10T08:40:59.523 回答
2

基于@Bruce 的回答,可以使用 recyclerview 解决错误,如下所示:

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View currentFocus = ((Activity)context).getCurrentFocus();
        if (currentFocus != null) {
            currentFocus.clearFocus();
        }
}
于 2016-06-22T06:50:47.727 回答
1

在我的情况下,它与windowSoftInputMode="adjustPan"列表元素(标题视图)上的 、 listView 和 editText 相关。

为了解决这个问题,我在活动完成之前调用了隐藏软键盘方法。

public void hideKeyboard(Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    View focusView = activity.getCurrentFocus();
    if (focusView != null) {
        inputMethodManager.hideSoftInputFromWindow(focusView.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    }
}
于 2015-05-15T08:51:53.533 回答
1

如果此处建议的解决方案均不适用于您...

我遇到了类似的错误,并注意到它是由我的用户设备报告的(在崩溃后),但没有明确解释导致它的原因(与问题中显示的日志相同) - 更具体地说,该问题仅发生在三星上Galaxy(包括 S6)设备(但不在 Nexus 设备或其他设备上,这就是我的测试最初未能揭示问题的原因)。因此,首先,值得检查问题是否是特定于设备的。

我后来发现,当三星虚拟键盘显示在文本字段上时按下后退按钮时,应用程序会崩溃并抛出此错误 - 但并非总是如此!

实际上,导致崩溃的文本字段也恰好显示在启用了 fillViewPort="true" 的滚动视图中。

我发现从滚动视图中删除 fillViewPort 选项不会与显示/隐藏的三星键盘冲突。我怀疑这个问题的部分原因是三星键盘与普通的 Nexus 键盘是不同的虚拟键盘,这就是为什么只有一部分用户遇到这个问题并且它只会在他们的设备上崩溃。

作为一般规则,如果此处建议的其他解决方案均不适用于您,我会检查问题是否是特定于设备的,并尝试简化我正在处理的视图,直到找到“罪魁祸首组件”(组件并认为,我应该补充一点,崩溃日志中没有报告 - 所以我只是偶然发现了导致问题的特定视图!)。

抱歉,我不能更具体,但如果有人遇到类似但无法解释的问题,我希望这能为进一步调查提供一些指导。

于 2016-07-11T19:58:55.793 回答
0

我的答案与此处的大多数答案有关,但我只想补充一点,就我而言,发生此崩溃是由于删除了带有当前焦点的编辑文本的行。

所以我所做的就是重写适配器的remove方法,并查询被删除的行是否包含当前的焦点编辑,如果是,则清除焦点。

这为我解决了它。

于 2014-07-11T11:41:52.227 回答
0

我正在使用 RecyclerView 并且所提供的解决方案都没有奏效。删除项目时出现错误。

起作用的是覆盖适配器的“onItemDismiss(int position)”,以便它在删除项目之前首先执行“notifyDataSetChanged()”,然后在删除项目后执行“notifyItemRemoved(position)”。像这样:

// Adapter code
@Override
public void onItemDismiss(int position) {
    if (position >= 0 && getTheList() != null && getTheList().size() > position) {
        notifyDataSetChanged();  // <--- this fixed it.
        getTheList().remove(position);
        scrollToPosition(position);
        notifyItemRemoved(position);
    }
}

还要在 TabFragment 中覆盖“removeAt(int position)”以调用新的清理代码,如下所示:

// TabFragment code
@Override
public void removeAt(int position) {
    mAdapter.onItemDismiss(position);
    mAdapter.notifyItemRemoved(position); // <--- I put an extra notify here too
}
于 2016-12-18T19:40:51.863 回答
0

我用 <android.support.v4.widget.NestedScrollView 替换了 ScrollView,它现在工作正常。

于 2021-03-17T13:43:14.440 回答