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

我有一个ListView与一些可调焦的组件(主要是EditText的)。 是的,我知道这不是完全推荐,但总的来说,几乎所有的东西都运行良好,重点放在了必须去的地方(只需要进行一些调整)。 无论如何,我的问题是用手指滚动列表时出现奇怪的竞态条件,然后在显示IME键盘时突然使用轨迹球。 有些东西必须走出界限,并在此处offsetRectBetweenParentAndChild()方法,并抛出IllegalArgumentException

问题是,这个exception抛出任何块,我可以插入try / catch(据我所知)。 所以这个问题有两个有效的解决scheme:

  1. 有人知道为什么抛出这个exception,以及如何阻止它发生
  2. 有人知道如何把一个try / catch块放在什么地方,至less让我的应用程序能够存活。 据我所知,问题是关注的问题,所以绝对不应该杀死我的应用程序(这是做什么)。 我试图覆盖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) 

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

所以我试试这个:
追加一个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(); } } } } 

尝试这个

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

编辑:

另见: 更好的解决scheme

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

它处理的是问题的症状,但并没有解决实际的原因。

如何重现问题:

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

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

ListView可以循环使用它们,并且可以像大家所知道的那样再次使用它们,但是有时它不需要立即使用一个已经离开屏幕的视图,所以它可以保存以备将来使用,而且不需要再显示它将它分离导致view.mParent为null。 然而,键盘需要知道如何传递input,并通过select聚焦的视图或EditText来做到这一点。

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

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

解决scheme:

我们需要听一个事件,告诉我们什么时候视图已经走到旁边,不再附加,幸运的是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); } } } }); 

对于什么是值得的(或者谁是这个失败者),我已经放弃了这个Activity的ListView方法。 除了随机崩溃之外,不设置windowSoftInputMode="adjustPan"可以正确地获得焦点行为,这会打开一堆其他的蠕虫。 相反,我只是去了一个“简单的”滚动视图,这一直很好。

我面临着同样的问题,并find了这个解决scheme – 在OnGroupCollapseListener/OnGroupExpandListenerOnScrollListenerExpandableListView我清晰OnGroupCollapseListener/OnGroupExpandListener和隐藏强制键盘。 另外不要忘记在你的活动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究竟是需要还是不需要,它可能是无用的。

我也遇到了这个问题,而且validcat的解决scheme也为我工作,但是我不得不调用getWindow().getCurrentFocus().clearFocus()

我用布鲁斯的答案进行了一些微调。

我需要adjustregize在我的活动,而不是adjustpan,但是当我尝试它错误再次发生。
我用NestedScrollViewreplace了ScrollView,现在工作正常。 希望这可以帮助别人!

在我的情况下,它是与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); } } 

如果这里build议的解决scheme都不适用于您…

我也遇到过类似的错误,并且注意到我的用户的设备(在崩溃之后)没有任何明确的解释(与问题中显示的日志相同) – 报告只发生在三星Galaxy(包括S6)设备(但不在Nexus设备或其他设备上,这就是为什么我的testing最初未能发现问题)。 所以,首先,值得检查问题是否是设备特定的。

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

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

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

作为一般规则,如果这里提出的其他解决scheme都不适用于您,我会检查问题是否是特定于设备的,并尝试简化我正在处理的视图,直到find“罪魁祸首组件”(组件并认为我应该补充说,没有在崩溃日志中报告 – 所以我只偶然发现导致问题的具体看法!)。

对不起,我不能更具体,但如果有人遇到类似的,但不明原因的问题,我希望这给一些进一步调查的指针。

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

 expandableListView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); 

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

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

这为我解决了。

基于@Bruce的答案,可以像这样解决与recyclerview的错误:

 @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View currentFocus = ((Activity)context).getCurrentFocus(); if (currentFocus != null) { currentFocus.clearFocus(); } } 

我正在使用RecyclerView和提出的解决scheme都没有工作。 删除项目时出现错误。

所做的工作是重写适配器的“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 }