Почему мой StaggeredGrid RecyclerView макет каждого элемента, а не только видимые?
У меня есть Сетка в шахматном порядке, содержащая 370 элементов с изображениями.
Я хочу убедиться, что элементы перерабатываются быстро, чтобы быть осторожным с памятью, но ViewHolder создается, а затем привязывается к каждому элементу в моем адаптере и не обращает внимания на то, видны ли дети
Я пробовал следующее
StaggeredGridLayoutManager lm = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
rv.setLayoutManager(lm);
rv.setItemViewCacheSize(20); //Has no effect
RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool();
pool.setMaxRecycledViews(0, 20);
rv.setRecycledViewPool(pool); //also has no effect
В журналах показано, что onCreateViewHolder и onBindViewHolder вызываются по 185 раз каждый. Затем onViewRecycled вызывается 185 раз, прежде чем возобновить вызовы onCreateViewHolder, пока мы не достигнем полного 370.
Это может быть проблемой понимания с моей стороны, но я думаю, что RecyclerView должен связывать только те виды, которые являются видимыми, или соблюдать только 20 видов или 20 в пуле + сколь угодно много умещается на экране. Как я могу сделать это с помощью StaggeredGridLayoutManager?
Если я слушаю изменения прокрутки и использую findFirstCompletelyVisibleItemPositions и findLastCompletelyVisibleItemPositions, это все равно охватывает каждый элемент адаптера, а не только те 6, которые помещаются на экране
Мой код адаптера
class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
static final int NUM_COLS = 3;
private final LayoutInflater mInflater;
private final List<GridItem> mEntries;
private int mLastExpanded; //stores where the last expanded item was
private OnCardClickListener mOnItemClick;
MyAdapter(Context context) {
super();
mEntries = new ArrayList<>();
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
void setOnTileClickListener(@Nullable OnCardClickListener listener) {
mOnItemClick = listener;
notifyDataSetChanged(); //recall bind logic
}
void setItems(Collection<GridItem> items) {
mEntries.clear();
mEntries.addAll(items);
sort();
}
@WorkerThread
private void sort() {
Collections.sort(mEntries, (thisEntry, otherEntry) -> {
int ret;
if (otherEntry == null || thisEntry.getCreated() == otherEntry.getCreated()) {
ret = 0;
} else if (thisEntry.getCreated() > otherEntry.getCreated()) {
ret = -1;
} else {
ret = 1;
}
return ret;
});
}
@Override
public int getItemCount() {
return mEntries.size();
}
private GridItem getItem(int position) {
return mEntries.get(position);
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MyViewHolder(mInflater.inflate(R.layout.li_grid_item, parent, false));
}
@Override
public void onViewRecycled(MyViewHolder holder) {
super.onViewRecycled(holder);
holder.onViewRecycled(); //clears bitmap reference
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
determineTileSize(holder, position);
holder.bind(getItem(position), mOnItemClick);
}
private void determineTileSize(MyViewHolder holder, int position) {
ViewGroup.LayoutParams cardParams = holder.getCardLayout().getLayoutParams();
StaggeredGridLayoutManager.LayoutParams gridItemParams = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
if (shouldBeExpanded(position)) {
cardParams.height = (int) holder.getCard().getResources().getDimension(R.dimen.spacing_card_large);
mLastExpanded = position;
gridItemParams.setFullSpan(true);
}
holder.getCardLayout().setLayoutParams(cardParams);
}
private boolean shouldBeExpanded(int position) {
return position > (mLastExpanded + NUM_COLS); //minimum 1 row between enlarged
}
}
Структура моего вида деятельности
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" ...>
<android.support.design.widget.AppBarLayout ...>
<android.support.design.widget.CollapsingToolbarLayout ...>
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" ... />
<android.support.design.widget.TabLayout ...
app:layout_collapseMode="pin"
android:layout_width="wrap_content"
android:layout_height="?attr/actionBarSize" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<FrameLayout
android:id="@+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="@dimen/height_backdrop"
android:minHeight="@dimen/height_backdrop"
android:background="@color/colorAccent"
android:visibility="gone"
app:elevation="@dimen/spacing_narrow"
app:behavior_peekHeight="0dp"
app:behavior_hideable="true"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior" />
</android.support.design.widget.CoordinatorLayout>
Макет фрагмента
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" ...>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/grid_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<!-- Empty and loading views -->
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>
2 ответа
Проблема:
Причина, по которой вы сталкиваетесь с этим, заключается в том, что вы добавили RecyclerView
в NestedScrollView
,
Причина:
Я не впервые слышу об этой проблеме, я и, вероятно, все, кто пытался поставить RecyclerView
в NestedScrollView
столкнулся с этой проблемой (если заметил).
Насколько я мог понять причину, это потому, что когда вы размещаете RecyclerView
в NestedScrollView
, он не может определить точную высоту, необходимую для RecyclerView
, Обычно разработчик предполагает для этого (простыми словами), что высота RecyclerView должна быть равна match_parent, как только все вышеперечисленные виды исчезнут с экрана. Но, к сожалению, это не так.
Это делает RecyclerView
каким-то образом wrap_content добавляет все его виды и затем измеряет их высоту (поправьте меня, если я ошибаюсь). Не уверен в возможной ошибке или ожидаемом поведении, но я верю NestedScrollView
должен иметь возможность обрабатывать этот случай явно, в противном случае, добавив RecyclerView
в NestedScrollView
совершенно бесполезен, так как не перерабатывает представления, полностью уничтожая RecyclerView
концепция и, следовательно, потребляя много памяти.
Временное решение:
Просто удалите RecyclerView
от NestedScrollView
так что он может правильно использовать виды.
ПРИМЕЧАНИЕ: ответ может быть не на 100% правильным, поскольку он полностью основан на моих личных наблюдениях и опыте. Любое лучшее решение или улучшения в ответе приветствуются.
Вопрос в NestedScrollview
пространство, доступное для recyclerview
не определяется.
Ты можешь использовать android:fillViewport="true"
делать NestedScrollView
измерить RecyclerView
, RecyclerView
заполнит оставшуюся высоту. так что если вы хотите прокрутить NestScrollView
Вы можете установить minHeight RecyclerView.