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

问题描述 投票:48回答:15

我有一个ListView,里面有一些可聚焦的组件(主要是EditTexts)。是的,我知道这不是完全推荐的,但总的来说,几乎所有东西都工作正常,重点放在必须去的地方(我需要进行一些调整代码)。无论如何,我的问题是,当用手指滚动列表然后在显示IME键盘时突然使用轨迹球时,存在一种奇怪的竞争条件。有些东西必须超出范围并得到回收,此时offsetRectBetweenParentAndChild()方法必须开始投掷IllegalArgumentException

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

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

堆栈跟踪:

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)
android viewgroup illegalargumentexception viewroot
15个回答
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();
                }
            }
        }

    }

2
投票

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

public class FixFocusErrorNestedScrollView extends NestedScrollView {
@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。


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

1
投票

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

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

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

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

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

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

对不起,我不能更具体,但我希望如果有人遇到类似但无法解释的问题,我会给出进一步调查的一些指示。


0
投票

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

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

这解决了我。


0
投票

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

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

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

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
}

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

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


10
投票

对于它的价值(或者无论谁遇到这种情况),我已经放弃了此活动的ListView方法。除了随机崩溃之外,如果没有设置打开一堆其他蠕虫的windowSoftInputMode="adjustPan",几乎不可能正确地获得焦点行为。相反,我只是去了一个“简单”的ScrollView,它一直很好用。


6
投票

我遇到了同样的问题,并发现了这个解决方案 - 在OnGroupCollapseListener/OnGroupExpandListenerOnScrollListenerExpandableListView我明确聚焦和隐藏强制键盘。另外不要忘记在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
投票

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

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


3
投票

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


2
投票

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

expandableListView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);

2
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.