17

У меня есть список из 13 элементов (хотя элементы могут быть добавлены или удалены), позиции 0-12. Когда первый фрагмент, содержащий RecyclerView, сначала отображается, только позиции с 0 по 7 видны пользователю (позиция 7 отображается только наполовину). В моем адаптере I Log каждый раз, когда держатель вида привязан/привязан (idk, если здесь применяется грамматика) и записывайте его позицию.onBindViewHolder() никогда не вызывается при просмотре в позиции, хотя RecyclerView.findViewHolderForAdapterPosition() возвращает null в этой позиции

адаптер

@Override 
public void onBindViewHolder(final ViewHolder holder, final int position) { 
    Log.d(TAG, "onBindViewHolder() position: " + position); 
    ... 
} 

С моей Log я вижу, что позиции 0-7 связаны:

Log from Adapter

У меня есть selectAll() метод, который получает каждый ViewHolder положением адаптера. Если возвращенный holder НЕ null Я использую возвращенный holder, чтобы обновить представление, чтобы показать его. Если возвращенный держатель IS null я называю selectOnBind() методом, который помечает вид в том обновлении позиции, чтобы показать, что он выбран, когда он переплетен, а не в реальное время, так как он в настоящее время не показан:

public void selectAll() { 
    for (int i = 0; i < numberOfItemsInList; i++) { 
     MyAdapter.ViewHolder holder = (MyAdapter.ViewHolder) 
       mRecyclerView.findViewHolderForAdapterPosition(i); 

     Log.d(TAG, "holder at position " + i + " is " + holder); 

     if (holder != null) { 
      select(holder); 
     } else { 
      selectOnBind(i); 
     } 
    } 
} 

В этом методе я Logholder вместе со своим положением:

Log from selectAll()

Так до этого момента все кажется нормальным. У нас есть позиции 0-7, и в соответствии с Log эти позиции связаны. Когда я ударил selectAll(), не меняя видимые виды (прокрутка), я вижу, что позиции 0-7 определены и 8-12 - null. Все идет нормально.

Вот где это становится интересным. Если после вызова selectAll() я прокручиваю дальше вниз по позициям 8 и 9 списка, они не показывают, что они выбраны.

При проверке Log я вижу, что это потому, что они никогда не связаны, хотя они, как сообщалось, null:

Adapter Log after scroll

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

EDIT (6-29-16)
После обновления AndroidStudio я не могу показаться, чтобы воспроизвести ошибку. Он работает так, как я ожидал, привязывая нулевые представления. Если эта проблема возникнет, я вернусь к этому сообщению.

+0

Неправильная практика выбора вашей строки, как и вы (и она не работает), лучший подход - иметь список (или карту) элемента select, а затем в поле «onBind» в вашем списке или в не. если он существует, измените строку на выбранную, иначе покажите нормальную строку. –

+0

Это то, что у меня есть. Но если вы только обновите их в 'onBind', пользователь не увидит изменения в видимых элементах, пока они не будут привязаны снова (это может быть' onResume' или прокрутка). Я не использовал лишний код, чтобы не усложнять вопрос. Хотя я хотел бы найти еще более чистый способ, чем то, как у меня есть, и вы упомянули. – YoungCoconutCode

+0

вы можете вызвать 'notifyDataSetChanged' или' notifyItemChanged' для вызова 'onBind' вручную –

ответ

12

Это происходит потому, что:

  • Представления не добавляются к recyclerview (getChildAt не будет работать, и будет возвращать нуль для этой позиции)
  • Они кэшируются также (onBind не будет называться)

Вызов recyclerView.setItemViewCacheSize(0) исправит эту проблему.

Поскольку значение по умолчанию равно 2 (private static final int DEFAULT_CACHE_SIZE = 2; в RecyclerView.Recycler), вы всегда получите 2 мнения, которые не будет вызывать onBind, но которые не добавлены в утилизатор

+0

recyclerView.setItemViewCacheSize (0) не является хорошей практикой, так как мы теряем производительность. –

+0

Да, я согласен с @TinTran. Пожалуйста, см. Править выше и спасибо за ваш вклад. – YoungCoconutCode

+2

Это просто объяснение, почему это происходит. Кажется странным, что обновление AndroidStudio изменило поведение, которое происходит с момента выхода recyclerview. –

5

В своих взглядах случае на позиции 8 и 9 не перерабатываются, их отстраняют от окна и снова присоединяют. И для этих отключенных просмотров onBindViewHolder не вызывается, называется только onViewAttachedToWindow. Если вы переопределите эти функции в своем адаптере, вы можете видеть, что я говорю.

@Override 
    public void onViewRecycled(ViewHolder vh){ 
     Log.wtf(TAG,"onViewRecycled "+vh); 
    } 

    @Override 
    public void onViewDetachedFromWindow(ViewHolder viewHolder){ 
     Log.wtf(TAG,"onViewDetachedFromWindow "+viewHolder); 
    } 

Теперь для того, чтобы решить проблему, то вы должны следить за мнения, которые должны были переработаны, но отделиться, а затем сделать ваш процесс раздела на

@Override 
    public void onViewAttachedToWindow(ViewHolder viewHolder){ 
     Log.wtf(TAG,"onViewAttachedToWindow "+viewHolder); 
    } 
+0

См. Править выше, и спасибо за ваш вклад. – YoungCoconutCode

0

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

class MyModel{ 
    String name; 
    int age; 
} 

Если вы отслеживаете вид выбран или нет, то ввести одно логическое значение модели. Теперь это будет выглядеть -

class MyModel{ 
    String name; 
    int age; 
    boolean isSelected; 
} 

Теперь ваш флажок будет выбран/деинсталлировать выбирается на основе нового флага IsSelected (в onBindViewHolder()). При каждом выборе в представлении изменится значение соответствующего выбранного значения модели на значение true, а при невыбранном изменении - на false. В вашем случае просто запустите цикл, чтобы изменить значение IsSelected всей модели на true, а затем наберите notifyDataSetChanged().

Для примера, давайте предположим, ваш список является

ArrayList<MyModel> recyclerList; 
private void selectAll(){ 
    for(MyModel myModel:recyclerList) 
     myModel.isSelected = true; 
    notifyDataSetChanged(); 
} 

Мое предложение, в то время как с помощью recyclerView или ListView менее пытаться играть с видом.

Так что в вашем случае -

@Override 
public void onBindViewHolder(final ViewHolder holder, final int position) { 
    holder.clickableView.setTag(position); 
    holder.selectableView.setTag(position); 
    holder.checkedView.setChecked(recyclerList.get(position).isSelected); 
    Log.d(TAG, "onBindViewHolder() position: " + position); 
    ... 
} 

@Override 
public void onClick(View view){ 
    int position = (int)view.getTag(); 
    recyclerList.get(position).isSelected = !recyclerList.get(position).isSelected; 
} 

@Override 
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 
     int position = (int)buttonView.getTag(); 
     recyclerList.get(position).isSelected = isChecked; 
} 

Надеется, что это поможет вам, пожалуйста, дайте мне знать, если вам нужна дополнительная объяснение :)

+0

Это будет работать, кроме случаев, когда пользователь выбирает вид, который является видимым и, следовательно, уже привязан? – YoungCoconutCode

+0

свяжите позицию с каждым щелчком мыши (с помощью setTag) в onBindViewHolder(). Поэтому всякий раз, когда вы получаете обратный вызов onClick или onChangeListner, просто получите помеченную позицию и измените соответствующую модель. – Neo

+0

Прошу прощения, но я не уверен, что следую – YoungCoconutCode

0

Так что я думаю, что вы вопрос отвечают ниже по @Pedro Оливейры , Основной смысл RecycleView заключается в том, что он использует специальные алгоритмы кэширования ViewHolder в любое время. Поэтому следующий onBindViewHolder (...) может не работать, например. если вид статичен или что-то еще.

И о вашем вопросе, который вы думаете использовать RecycleView для динамических изменений. НЕ ДЕЛАЙТЕ ЭТО! Поскольку RecycleView делает недействительными представления и имеет систему кэширования, у вас будет много проблем.

Используйте LinkedListView для этой задачи!

1

Ответы Педро Оливейры и Зарты отлично подходят для понимания проблемы, но я не вижу никаких решений, которым я доволен.

Я считаю, что у вас есть 2 хорошие варианты в зависимости от того, что вы делаете:

Вариант 1

Если вы хотите onBindViewHolder() получить колл для зрения закадрового независимо, если он кэшируется/удаленные или нет, то вы можете сделать:

RecyclerView.ViewHolder view_holder = recycler_view.findViewHolderForAdapterPosition(some_position); 

if (view_holder != null) 
{ 
    //manipulate the attached view 
} 
else //view is either non-existant or detached waiting to be reattached 
    notifyItemChanged(some_position); 

идея в том, что если представление кэшируется/отдельностоящий, то notifyItemChanged() покажет адаптер, просмотр которого недействителен, что приведет к вызову onBindViewHolder().

Вариант 2

Если вы хотите, чтобы выполнить частичную замену (а не все внутри onBindViewHolder()), то внутри onBindViewHolder(ViewHolder view_holder, int position), вам нужно хранить position в view_holder, и выполнить изменение вы хотите в onViewAttachedToWindow(ViewHolder view_holder).

Я рекомендую вариант 1 для простоты, если ваш onBindViewHolder() не делает что-то интенсивное, как возиться с растровыми изображениями.

+1

Вариант 1 мне помог. Благодаря! – tingyik90

 Смежные вопросы

  • Нет связанных вопросов^_^