【问题标题】:Preventing/catching "IllegalArgumentException: parameter must be a descendant of this view" error防止/捕获“IllegalArgumentException:参数必须是此视图的后代”错误
【发布时间】:2011-10-29 08:33:58
【问题描述】:

我有一个 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)

【问题讨论】:

  • 对于它的价值(或任何偶然发现的人),我已经放弃了 ListView 方法来处理这个 Activity。除了随机崩溃之外,如果不设置windowSoftInputMode="adjustPan",几乎不可能正确地获得焦点行为,这会打开一堆其他的蠕虫罐头。相反,我只是选择了一个“简单的”ScrollView,效果很好。
  • 用砖墙做类似的事情,ListViewEditTexts 只是不值得。实际上,这个问题对我来说是个大问题。
  • 我有来自生产应用程序的完全相同的崩溃堆栈,我自己无法模拟它。我有一个带有 ListView 的屏幕,其中包含动态填充的 EditViews、Chekcboxes 和 Spinners,具体取决于从服务器获取的数据。崩溃很烦人。我应该使用什么动态父视图作为动态可聚焦视图(EditViews、Check、Spinners)的容器?
  • 另外,参考 OP 的报价:Yeah, I know this isn't exactly recommended。是否有任何参考支持这一点并解释原因?非常感谢!
  • ListViewEditTexts 的替代方法是将TextViews 样式设置为EditTexts,当单击时会弹出带有EditText 样式的Dialog在里面。

标签: android viewgroup illegalargumentexception viewroot


【解决方案1】:

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

解决了问题的症状,但没有解决实际原因。

如何重现问题:

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

【讨论】:

  • 你有重现异常的示例代码吗?
  • 我没有示例代码,但我认为它很容易重现:创建一个 listView,创建很多 listView 行,其中包含编辑文本,运行,然后专注于 editTexts (键盘将被打开),现在滚动以使该行不会出现在屏幕上(当键盘仍处于打开状态时)
  • 这是这里提供的唯一对我有用的解决方案。
  • 很棒的解释!
  • 我在使用 EditText RecyclerView 时遇到了同样的异常。有没有人发现在回收站视图中面临?
【解决方案2】:

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

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

    }

【讨论】:

  • 这对我有用。在我的场景中,我有一个带有 EditText 设置为标题的 ExpandableListView,在活动的清单中使用 android:windowSoftInputMode="adjustPan" 。在 IME 键盘启动时向下滚动 ExpandableListView 会可靠地使设备崩溃。这解决了它,谢谢。
  • 在我的情况下,当焦点位于该行的编辑文本内时,当我从列表视图中删除一行时,就会出现问题。这个答案导致我在我的 onClick 侦听器中添加parent.clearChildFocus(parent.findFocus()) 以进行删除点击。在我调用删除该位置的项目之前,我将它放在一个 try-catch 中。我的 onClick 监听器在我的适配器的 getView 中,parentViewGroup
  • 我有同样的问题@Mira_Cole。你能再解释一下吗?
【解决方案3】:

试试这个

 @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

【讨论】:

  • 尝试添加一些关于这段代码的作用以及它如何解决原始问题的解释。
  • 我不太确定这个解决方案能适应这类问题。在我看来,这个问题是因为 listView 和 IME KeyBoard 之间的冲突。当您将 EditText 放入 ListView 并尝试对其进行编辑时,IME 键盘可能会在 EditText 上显示提示视图悬停,并且当您滑动 ListView 直到聚焦的 EditText 消失时,应用程序崩溃。所以我认为提示视图与 ListView 存在某种冲突。
【解决方案4】:

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

【讨论】:

  • 您能帮忙解决这个问题吗?我无法重现该问题,但它经常在生产中使用此堆栈跟踪崩溃。我有一个用于 SearchFilter 的 ListView,其中包含基于服务器响应的动态填充(可聚焦)元素,例如 EditText、Spinners。我是否需要摆脱我的 ListView 并将它们托管在一些非适配器视图中?
【解决方案5】:

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

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

【讨论】:

  • 太棒了!这个安静的,“埋没”的回复帮助了我!
  • 太棒了。非常感谢
【解决方案6】:

我有最简单但不是很好的解决方案。只需扩展 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。

【讨论】:

  • 当清晰的焦点和其他事情不起作用时,这个解决方案就起作用了。
【解决方案7】:

我遇到了同样的问题并找到了这个解决方案 - 在 OnGroupCollapseListener/OnGroupExpandListenerOnScrollListener for 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 是否需要,它可能没用。

【讨论】:

    【解决方案8】:

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

    【讨论】:

    • 我在片段中使用了视图绑定。它有一个 Recyclerview,我正在向现有列表提交一个更新的列表,在该列表中应用程序崩溃了,给出了这个错误“参数必须是这个视图的后代”,发生在我在 EditText 中输入值之后。错误是由于即使隐藏键盘后当前编辑文本上仍然存在焦点。上面的解决方案就像告诉 recyclerview 不再需要焦点一样。我已经使用binding.getRoot().clearFocus() 完成了
    【解决方案9】:

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

    expandableListView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
    

    【讨论】:

    • 对我来说,这在 RecyclerView 上也很有效
    【解决方案10】:

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

    【讨论】:

    • 这个答案帮助我知道问题出在哪里。但是,该建议并没有解决问题。 This answer 引导我找到解决方案,即在删除行之前清除焦点。 +1 帮助我解决此问题。谢谢!
    • 你是如何强制键盘的?我在这里面临完全相同的问题
    【解决方案11】:

    根据@Bruce 的回答,可以像这样使用 recyclerview 解决错误:

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

    【讨论】:

    • 我也面临这个问题,我正在使用回收站视图。我使用您的解决方案,但它不适用于回收站视图。您是否尝试过使用回收站视图?
    • 就我而言,它不起作用,@Hadi 的回答对我有用。
    【解决方案12】:

    在我的例子中,它与列表元素(标题视图)上的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);
        }
    }
    

    【讨论】:

      【解决方案13】:

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

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

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

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

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

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

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

      【讨论】:

      • 我在 LG Nexus 6 上面对它,所以它不是特定于设备的。
      • 问题是这个问题是一个与“ViewGroup”有关的 Android 运行时错误,因此不会有一种解决方案适用于所有人,具体取决于您的代码的确切原因首先抛出这个问题。现在要明确一点,我并不是说这个问题总是特定于设备(我从“如果没有其他解决方案适用于您”开始),但它肯定是在我遇到的 ViewGroup 问题的特定实例中。如果它不是特定于您的设备,也许可以尝试其他解决方案。
      • 这个问题几乎可以肯定不是由于键盘,而是由于三星出于某种原因对 ListView 源代码进行了某种重大更改。每次我在三星设备上调试应用程序时,只要执行进入 ListView,源代码显示就会出现问题,这是因为源不匹配。
      【解决方案14】:

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

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

      这为我解决了。

      【讨论】:

      • recyclerview 你会怎么做?我认为我的问题正是您所描述的。但我不知道如何检测该行是否被删除。你能提供一个示例代码吗?谢谢...
      • 哇,过去的爆炸......不幸的是,太古老了,无法回复。您能否将问题最小化为易于重现的问题?我可以看看
      【解决方案15】:

      我正在使用 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
      }
      

      【讨论】:

        【解决方案16】:

        我用

        【讨论】:

        • 这与哈迪的回答有何不同?
        • 它仍然存在。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多