RecyclerView.getChild(index) показывает null при прокрутке списка (индекс перепутался)

Я использую SwipeableRecyclerView для своего приложения для Android, чтобы включить перелистывание для моего recyclerView. RecyclerView содержит список карточек.

Я пытался реализовать функцию отмены для карточек, которые будут удаляться при свайпе влево (первое смахивание показывает отмену, следующее смахивание запускает удаление)

Я пытаюсь использовать следующий код (частично работает, я думаю)

SwipeableRecyclerViewTouchListener srvTouchListner = new SwipeableRecyclerViewTouchListener(rvTimerList,
            new SwipeableRecyclerViewTouchListener.SwipeListener(){

                @Override
                public boolean canSwipe(int i) {
                    return true;
                }

                @Override
                public void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] ints) {
                    for(int position : ints){
                        View view = recyclerView.getChildAt(position);
                            if (view.getTag(R.string.card_undo) == null) {
                                if(viewStack == null) {
                                    saveToViewStack(position, view);
                                    final ViewGroup viewGroup = (ViewGroup) view.findViewById(R.id.time_card2);
                                    view.setTag(R.string.card_undo, "true");
                                    viewGroup.addView(view.inflate(TimerSummary.this, R.layout.timeslot_card_undo, null));
                                }
                            } else {
                                Log.d(TAG, "Removing Item");
                                deleteTimeSlot(timerInstanceList.get(position));
                                Toast.makeText(TimerSummary.this, "Deleted!", Toast.LENGTH_SHORT).show();
                                timerInstanceList.remove(position);
                                finalSummaryAdapter.notifyItemRemoved(position);
                            }

                    }
                    finalSummaryAdapter.notifyDataSetChanged();
                }
                @Override
                public void onDismissedBySwipeRight(RecyclerView recyclerView, int[] ints) {
                    for (int position:ints){
                        View view = recyclerView.getChildAt(position);
                        if(view.getTag(R.string.card_undo) != null && view.getTag(R.string.card_undo).equals("true")){
                            viewStack = null;
                            recyclerView.setAdapter(finalSummaryAdapter);
                        }
                    }

                }
            });

когда Предметов больше (требуется прокрутка)

View view = recyclerView.getChildAt(position);

возвращает нулевую ссылку, которая вызывает сбой приложения.

Я сомневаюсь, что использую неправильный метод для просмотра. Я должен использовать что-то, связанное с просмотрщиком, я на самом деле немного запутался в том, как получить вид, который вы хотите от зрителя.

Если кто-нибудь может поделиться чем-нибудь, что поможет, это будет здорово! Я буду рад предоставить любую дополнительную информацию, если кто-то захочет,


person P-RAD    schedule 16.06.2015    source источник


Ответы (3)


Вы должны использовать findViewHolderForAdapterPosition. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#findViewHolderForAdapterPosition(int)

Имейте в виду, что он вернет null, если позиция не выложена (например, за пределами или удалена).

person yigit    schedule 17.06.2015
comment
Как мне получить доступ к любым представлениям из этого средства просмотра, могу ли я изменить макет и создать новый? - person P-RAD; 17.06.2015
comment
В любом случае, я использую карточку, которая сначала скрыта, чтобы показывать только тогда, когда пользователь пытается удалить элемент. Таким образом, я могу избежать беспорядка, созданного, когда я пытаюсь получить доступ к представлениям непосредственно из recyclerView . Этот работает нормально, я буду использовать эту концепцию, пока не смогу правильно просматривать просмотры из recyclerView. - person P-RAD; 17.06.2015
comment
@yigit Можете ли вы уточнить немного больше? Вы сказали, что должны использовать... Это ожидаемое поведение или ошибка? (Почему?). Я вижу RecyclerView как прокручиваемый вид сам по себе, так что это немного странно, если вы спросите меня. - person GMan; 27.08.2015
comment
RecyclerView представляет потенциально длинный список элементов с небольшим количеством просмотров (достаточно, чтобы заполнить экран). GetChildAt является методом ViewGroup и не имеет ничего общего с позициями адаптера. Таким образом, когда RV отображает элементы с 20 по 30 адаптера из 100 элементов, в группе просмотра есть только 10 дочерних элементов (дочерний элемент 0 — это элемент адаптера 20 и т. д.). Вот почему RV предоставляет методы индексации адаптера. - person yigit; 27.08.2015
comment
Спасибо, теперь я понимаю. - person GMan; 27.08.2015
comment
В этом отношении у RecyclerView есть некоторые странности. Количество дочерних элементов группы представлений фактически изменяется, если представление удаляется и вызывается notifyItemRemoved. То есть, если до этого было 7, то после будет 8. Если я правильно понимаю, это связано с поддержкой анимации, поэтому представления должны оставаться НЕВИДИМЫМИ в течение некоторого времени, пока не будут очищены. - person slott; 31.10.2015
comment
Да. Мы могли бы реализовать это по-другому, если бы реализовали RecyclerView в основной структуре. К сожалению, это будет означать, что никто не сможет использовать его в течение 2 лет :/. Если вам интересны подробности, я написал о том, как это работает, здесь: birbit.com/recyclerview-animations-part-1-how-animations-work - person yigit; 31.10.2015
comment
Полезный ответ. В моем случае подходил findViewHolderForLayoutPosition(), так как мне нужно было анимировать строку сразу после вызова notifyDataSetChanged() на адаптере. findViewHolderForAdaterPosition() действительно вернет null в этом случае. - person PPartisan; 01.11.2015
comment
Он вернет null, потому что когда вы вызываете notifyDataSetChanged, все представления недействительны, пока не произойдет другой расчет макета. - person yigit; 01.11.2015
comment
у меня такая же проблема, как у getChild. После выкладки оба иногда возвращают null. - person coolcool1994; 24.08.2020

Поскольку у меня недостаточно очков репутации, чтобы прокомментировать ответ @yigit, я подумал, что дам способ получить все viewHolder из RecyclerView независимо от того, что в данный момент находится в viewGroup. Даже с помощью findViewHolderForAdapterPosition и ForLayoutPosition мы все еще можем получить нулевой экземпляр ViewHolder в RecyclerView.

Чтобы обойти это, мы можем использовать findViewHolderForItemId[1] и передать идентификатор viewHolder в адаптер с помощью RecyclerView.Adapter#getItemId(int position)[2]

for (int i = 0; i < mAdapter.getItemCount(); i++) {
    RecyclerView.ViewHolder holder = 
            mRecyclerView.findViewHolderForItemId(mAdapter.getItemId(i));
}
  1. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#findViewHolderForItemId(int)
  2. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemId(int)
person Brandon McAnsh    schedule 18.09.2015
comment
Это интересный ответ. Я попробовал и обнаружил, что findViewHolderForItemId(int) просто хочет позицию int, а не Long, которую возвращает getItemId(int). - person Gi0rgi0s; 12.10.2015

Используйте методы findFirstCompletelyVisibleItemPosition() и findLastVisibleItemPosition() в LayoutManager вашего RecyclerView, чтобы узнать, виден ли дочерний элемент, который вы ищете, или нет. Если он виден, вы можете использовать getChild(index) для получения экземпляра ViewHolder этого конкретного дочернего элемента, и он не будет нулевым, иначе он может быть нулевым (если дочерний элемент не находится в нем). Если ваш ребенок не виден, вы просто вносите изменения в свой конкретный объект списка позиций, в следующий раз, когда он будет виден, будет обновлен пользовательский интерфейс.

if (selectedPosition >= linearLayoutManager.findFirstVisibleItemPosition()
                    && selectedPosition <= linearLayoutManager.findLastVisibleItemPosition()){
                    linearLayoutManager.getChildAt(selectedPosition).setBackgroundResource(0);
                }
person Shailendra Yadav    schedule 10.07.2017
comment
Отличное решение! Большое спасибо! - person Leenah; 24.04.2018