防止/捕获“IllegalArgumentException:参数必须是此视图的后代”错误

时间:2011-08-17 23:01:08

标签: android viewgroup illegalargumentexception viewroot

我有一个ListView,其中包含一些可聚焦的组件(主要是EditText s)。是的,我知道这不是完全推荐的,但总的来说,几乎所有东西都工作正常,重点放在必须去的地方(我需要进行一些调整代码)。无论如何,我的问题是,当用手指滚动列表然后在显示IME键盘时突然使用轨迹球时,会出现奇怪的竞争情况。某些东西必须超出范围并被回收,此时offsetRectBetweenParentAndChild()方法必须启动并抛出IllegalArgumentException

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

  1. 有人知道为什么抛出此异常以及如何阻止它发生
  2. 有人知道如何在某个地方放置一个try / catch块,至少让我的应用程序能够存活下来。据我所知,问题是焦点,所以它绝对不应该杀死我的应用程序(这是它正在做的)。我尝试覆盖ViewGroup的方法,但这两个offset*方法被标记为final。
  3. 堆栈追踪:

    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)
    

15 个答案:

答案 0 :(得分:31)

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

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

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();
                }
            }
        }

    }

答案 1 :(得分:26)

尽管Bruce's answer确实解决了这个问题,但它以非常残酷的方式解决了这个问题,因为它会在我们进行滚动后清除每个视图的焦点。

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

如何重现问题:

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

让我们先了解发生此问题的原因:

ListView回收其视图并再次使用它们众所周知,但有时它不需要使用立即离开屏幕的视图,以便将其保留以备将来使用,并且因为它不需要是显示它将分离它导致view.mParent为null。 但键盘需要知道如何将输入传递给它,它通过选择聚焦视图或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);
                }
            }
        }
    });

答案 2 :(得分:23)

试试这个

 @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
}

修改

另请参阅:Better Solution

答案 3 :(得分:10)

对于它的价值(或者偶然发现了什么),我放弃了此活动的ListView方法。除了随机崩溃之外,如果没有设置打开一堆其他蠕虫的windowSoftInputMode="adjustPan",几乎不可能正确地获得焦点行为。相反,我只是选择了一个“简单”的ScrollView,并且工作得非常好。

答案 4 :(得分:6)

我遇到了同样的问题并找到了这个解决方案 - 在OnGroupCollapseListener/OnGroupExpandListenerOnScrollListener ExpandableListView我明确了焦点并隐藏强制键盘。另外,请不要忘记为您的活动manifest设置windowSoftInputMode="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是否需要,否则它可能毫无用处。

答案 5 :(得分:5)

我稍微调整了布鲁斯的答案。

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

答案 6 :(得分:3)

我也遇到了这个问题,the solution by validcat对我有用,但我不得不打电话给getWindow().getCurrentFocus().clearFocus()

答案 7 :(得分:2)

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

expandableListView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);

答案 8 :(得分:2)

EditText中使用Recyclerview时遇到了同样的问题。经过大量的努力和尝试不同的选项,我发现在键盘打开后删除行后产生了这个问题。我通过强制关闭键盘并使用notifyItemRemoved(position)更改notifyDataSetChanged()来解决此问题。

答案 9 :(得分:1)

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

为了解决这个问题,我在活动结束前调用了hide soft keyboard方法。

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);
    }
}

答案 10 :(得分:1)

If none of the solutions suggested here apply to you...

I've experienced a similar error and noticed it was reported by my users' devices (after a crash) without any clear explanation on what was causing it (same as the log shown on the question) - more specifically the issue only happened on Samsung Galaxy (including S6) devices (but not on Nexus devices or others, which is why my testing initially failed to reveal the issue). So, first, it is worth checking if the issue is device specific or not.

What I later found is that when pressing the back button while a Samsung virtual keyboard was displayed on a text field, the application would crash throwing this error - but not always!

Indeed, the text field causing the crash also happened to be displayed within a scrollview with fillViewPort="true" enabled.

What I found is that removing the fillViewPort option from the scrollview would not conflict with the Samsung keyboard being displayed/hidden. I suspect the issue is partly due to the fact that Samsung keyboards are different virtual keyboards than the stock Nexus keyboards, which is why only a subset of my users were experiencing the issue and it would crash on their devices only.

As a general rule, and if none of the other solutions suggested here apply to you, I would check if the issue is device specific, and also attempt to simplify the view I am working on until I can find the "culprit component" (component and view that, I should add, wasn't reported in the crash logs - so I only stumbled on the specific view causing the issue by chance!).

Sorry I cannot be more specific, but I hope this gives some pointers for further investigation if someone experience a similar but unexplained issue.

答案 11 :(得分:0)

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

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

这解决了我。

答案 12 :(得分:0)

基于@Bruce答案,可以使用recyclelerview解决错误:

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

        View currentFocus = ((Activity)context).getCurrentFocus();
        if (currentFocus != null) {
            currentFocus.clearFocus();
        }
}

答案 13 :(得分: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
}

答案 14 :(得分:0)

我用