OnBindViewHolder() никогда не вызывается при просмотре в позиции, хотя RecyclerView.findViewHolderForAdapterPosition() возвращает значение null в этой позиции
У меня есть список из 13 элементов (хотя элементы могут быть добавлены или удалены), позиции 0-12. Когда первый фрагмент, содержащий RecyclerView, сначала отображается, только позиции с 0 по 7 видны пользователю (позиция 7 отображается только наполовину). В моем адаптере я Log
каждый раз, когда держатель вида привязан/привязан (idk, если здесь применяется грамматика) и записывайте его положение.
Адаптер
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
Log.d(TAG, "onBindViewHolder() position: " + position);
...
}
Из моего Log
я вижу, что позиции 0-7 связаны:
![Вход из адаптера]()
У меня есть метод 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);
}
}
}
В этом методе я Log
holder
вместе с его положением:
![Вход из selectAll()]()
Итак, до этого момента все кажется нормальным. У нас есть позиции 0-7, и в соответствии с Log
они связаны с позициями. Когда я ударил selectAll()
, не изменяя видимые виды (прокрутка), я вижу, что позиции 0-7 определены, а 8-12 - null
. Пока все хорошо.
Здесь, где это становится интересным. Если после вызова selectAll()
я прокручиваю дальше вниз, позиции 8 и 9 списка не показывают, что они выбраны.
При проверке Log
я вижу, что это потому, что они никогда не связаны, хотя они были сообщены как null
:
![Журнал адаптеров после прокрутки]()
Еще более запутанным является то, что это не происходит каждый раз. Если я впервые запустил приложение и проверил его, он может работать. Но, похоже, это происходит безуспешно после этого. Я предполагаю, что это имеет какое-то отношение к просмотрам, которые перерабатываются, но даже не нужно ли их связывать?
РЕДАКТИРОВАТЬ (6-29-16)
После обновления AndroidStudio я не могу воспроизвести ошибку. Он работает так, как я ожидал, привязывая нулевые представления. Если эта проблема возникнет, я вернусь к этому сообщению.
Ответы
Ответ 1
Это происходит потому, что:
- Представления не добавляются в recyclerview (
getChildAt
не будет работать и вернет null для этой позиции)
- Они также кэшируются (
onBind
не будет вызываться)
Вызов recyclerView.setItemViewCacheSize(0)
устранит эту "проблему".
Поскольку значение по умолчанию равно 2 (private static final int DEFAULT_CACHE_SIZE = 2;
в RecyclerView.Recycler
), вы всегда получите 2 представления, которые не будут вызывать onBind
, но которые не добавляются в recycler
Ответ 2
В вашем случае вид на позиции 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);
}
Ответ 3
Ответы Педро Оливейры и Зарты отлично подходят для понимания проблемы, но я не вижу никаких решений, которым я доволен.
Я считаю, что у вас есть 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()
не делает что-то интенсивное, как возиться с растровыми изображениями.
Ответ 4
Я думаю, что играть с видом - это не очень хорошая идея в 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;
}
Надеюсь, это поможет вам, пожалуйста, дайте мне знать, если вам нужно дальнейшее объяснение:)
Ответ 5
Итак, я думаю, что на ваш вопрос ответил @Pedro Oliveira. Основной смысл RecycleView заключается в том, что он использует специальные алгоритмы кэширования ViewHolder в любое время. Поэтому следующий onBindViewHolder (...) может не работать, например. если представление статично или что-то еще.
И о вашем вопросе, который вы думаете использовать RecycleView для динамических изменений Views. НЕ ДЕЛАЙТЕ ЭТО!. Поскольку RecycleView делает недействительными представления и имеет систему кеширования, у вас будет много проблем.
Используйте LinkedListView для этой задачи!
Ответ 6
Когда у вас есть большое количество элементов в списке, который вы перешли на recyclerview adapter
onBindViewHolder()
вы не столкнетесь с проблемой onBindViewHolder()
не выполняется во время прокрутки.
Но если в списке меньше предметов (я проверил размер списка 5), вы можете столкнуться с этой проблемой.
Лучшее решение - проверить list size
.
Пожалуйста, найдите пример кода ниже.
private void setupAdapter(){
if (list.size() <= 10){
recycler.setItemViewCacheSize(0);
}
recycler.setAdapter(adapter);
recycler.setLayoutManager(linearLayoutManager);
}