Предотвращение / отлов ошибки "IllegalArgumentException: параметр должен быть потомком этого представления"

У меня есть ListView с некоторыми фокусируемыми компонентами внутри (в основном EditTextс). Да, я знаю, что это не совсем рекомендуется, но в целом почти все работает нормально, и фокус направлен на то, что нужно (с некоторыми изменениями, которые мне пришлось кодировать). В любом случае, моя проблема в том, что при прокрутке списка пальцем возникает странное состояние гонки, а затем внезапно используется трекбол, когда отображается клавиатура IME. Что-то должно выйти за пределы и переработаться, и в этот момент offsetRectBetweenParentAndChild() метод должен пнуть и бросить IllegalArgumentException,

Проблема в том, что это исключение выбрасывается за пределы любого блока, в который я могу вставить try/catch (насколько я знаю). Таким образом, есть два правильных решения этого вопроса:

  1. Кто-то знает, почему выбрасывается это исключение и как его предотвратить.
  2. Кто-то знает, как разместить блок try / catch где-нибудь, чтобы хотя бы выжить моему приложению. Насколько я знаю, проблема в фокусе, так что это определенно не должно убивать мое приложение (что и делает). Я пытался переопределить ViewGroupметоды, но эти два 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)

18 ответов

Решение

Что бы это ни стоило (или кто бы ни споткнулся об этом), я отказался от подхода ListView для этого действия. Помимо случайных сбоев, практически невозможно правильно настроить поведение фокуса без установки windowSoftInputMode="adjustPan" который открывает кучу других банок червей. Вместо этого я просто выбрал "простой" ScrollView, и он отлично работал.

В то время как ответ Брюса действительно решает проблему, он делает это очень жестоко, что наносит вред UX, поскольку он очистит фокус каждого представления, как только мы сделаем прокрутку.

Это имеет дело с симптомом проблемы, но это не решает фактическую причину.

как воспроизвести проблему:

Ваш 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);
                }
            }
        }
    });

Прошу прощения, но мой предыдущий ответ не самый лучший способ решить эту проблему.

Итак, я пытаюсь это:
Добавьте ScrollListener к своей деятельности, когда 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
}

РЕДАКТИРОВАТЬ:

Смотрите также: лучшее решение

Я использовал ответ Брюса с небольшой поправкой.

мне было нужно adjustResize в моей деятельности вместо adjustpan но когда я попробовал это, ошибка произошла снова.
Я заменил ScrollView с <android.support.v4.widget.NestedScrollView и теперь работает нормально. Надеюсь, это поможет кому-то!

У меня самое простое, но не хорошее решение. Просто расширьте 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.

Я столкнулся с той же проблемой и обнаружил это решение - в OnGroupCollapseListener/OnGroupExpandListener а также OnScrollListener за 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 нужно или нет, это может быть бесполезно.

В случае расширяемого представления списка, если ваши дочерние элементы имеют редактируемый текст, вам нужно изменить фокусируемость перед потомками для расширяемого представления списка.

expandableListView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);

Я тоже столкнулся с этой проблемой, и у меня сработало решение validcat, но мне пришлось позвонить getWindow().getCurrentFocus().clearFocus(),

Я столкнулся с той же проблемой при использовании EditText в Recyclerview, После долгих усилий и попыток использовать другой вариант, я обнаружил, что после удаления строки, когда моя клавиатура открыта, возникает эта проблема. Я решил это силой закрыв клавиатуру и изменив notifyItemRemoved(position) с notifyDataSetChanged(),

Основываясь на ответе @Bruce, можно исправить ошибку с помощью recyclerview следующим образом:

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

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

В моем случае это было связано с 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);
    }
}

У меня возникает та же проблема, когда я использовал editText в элементе recyclerview. Если состояние edittext сфокусировано, а программная клавиатура открыта. Я пытаюсь закрыть свой текущий фрагмент, и приложение разбивается, поэтому я сделал это до завершения вызова, который я использовал

      binding.root.clearFocus()

И то же самое я сделал в адаптере

      holder.binding.root.clearFocus()

Если ни одно из предложенных здесь решений не относится к вам...

Я столкнулся с подобной ошибкой и заметил, что о ней сообщали устройства моих пользователей (после сбоя) без какого-либо четкого объяснения причины ее возникновения (аналогично журналу, показанному в вопросе) - более конкретно, проблема возникла только на Samsung Устройства Galaxy (включая S6) (но не на устройствах Nexus или других, именно поэтому в ходе моего тестирования не удалось выявить проблему). Итак, во-первых, стоит проверить, относится ли проблема к конкретному устройству или нет.

Позже я обнаружил, что при нажатии кнопки "Назад", когда виртуальная клавиатура Samsung отображалась в текстовом поле, приложение вылетало, вызывая эту ошибку - но не всегда!

Действительно, текстовое поле, вызывающее сбой, также отображалось в просмотре прокрутки с включенным fillViewPort="true".

Я обнаружил, что удаление параметра fillViewPort из представления прокрутки не будет конфликтовать с отображением / скрытием клавиатуры Samsung. Я подозреваю, что проблема отчасти связана с тем фактом, что клавиатуры Samsung отличаются от виртуальных клавиатур от стандартных клавиатур Nexus, поэтому проблема возникла только у части моих пользователей, и она зависала только на их устройствах.

Как правило, и если ни одно из других решений, предложенных здесь, не применимо к вам, я бы проверил, относится ли проблема к конкретному устройству, а также попытался бы упростить представление, над которым я работаю, до тех пор, пока не найду "компонент виновника" (компонент и посмотреть, что, я должен добавить, не было зарегистрировано в журналах сбоев - поэтому я наткнулся только на конкретное представление, вызвавшее проблему случайно!).

Извините, я не могу быть более конкретным, но я надеюсь, что это даст некоторые подсказки для дальнейшего расследования, если кто-то столкнется с подобной, но необъяснимой проблемой.

Мой ответ связан с большинством ответов здесь, но я просто хотел добавить, что в моем случае этот сбой произошел из-за удаления строки с текстом редактирования, который в настоящее время имел фокус.

Поэтому все, что я сделал, это переопределил метод удаления адаптера и запросил, содержит ли удаленная строка текущее редактирование фокуса, и если да, очистите фокус.

Это решило это для меня.

Я использую 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);
    }
}

Также выполните переопределение 'removeAt(int position)' в TabFragment, чтобы вызвать новый код очистки, например так:

// TabFragment code
@Override
public void removeAt(int position) {
    mAdapter.onItemDismiss(position);
    mAdapter.notifyItemRemoved(position); // <--- I put an extra notify here too
}

Я использовал подход, аналогичный подходу Сноу Альберта, но вместо того, чтобы перехватывать выброшенное исключениеonSizeChanged(), я отключил детскую фокусировку, чтобы предотвратить сбой:

      class RestrictedFocusabilityScrollView : NestedScrollView {

constructor(context: Context) : this(context, null, 0)

constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(
    context,
    attrs,
    defStyle
)

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    //Making children not focusable during layout to avoid the "parameter must be a descendant of this view" crash
    children.forEach { it.isFocusableInTouchMode = false }
    super.onSizeChanged(w, h, oldw, oldh)
    children.forEach { it.isFocusableInTouchMode = true }
}

}

Я заменил ScrollView на <android.support.v4.widget.NestedScrollView, и теперь он отлично работает.

Другие вопросы по тегам