Why does my StaggeredGrid RecyclerView place every element, not just visible?

I have a grid with steps containing 370 elements, with images.

I want the elements to be recycled quickly to be careful with the memory, but a ViewHolder is created and then attached to each element in my adapter and does not pay attention to whether children are visible.

I tried the following

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 

The onCreateViewHolder and onBindViewHolder log displays are called 185 times each. Then onViewRecycled is called 185 times before resuming calls to onCreateViewHolder until we reach full 370.

This may be a problem of understanding on my part, but I think that RecyclerView should only link the views that are visible, or honor only 20 views or 20 in the + pool, although many fit on the screen. How to do it using StaggeredGridLayoutManager?

If I listen to scroll changes and use findFirstCompletelyVisibleItemPositions and findLastCompletelyVisibleItemPositions, this still covers every element in the adapter, not just 6 that fit on the screen

My adapter code

 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 } } 

Layout structure of my activity

 <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> 

Fragment Layout

 <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> 
0
android layout-manager android-recyclerview
Nov 01 '16 at
source share
1 answer

Problem:

The reason you come across this is because you added RecyclerView to NestedScrollView .

Cause:

Not the first time I heard about this problem, I, and probably everyone who tried to put RecyclerView in NestedScrollView , ran into this problem (if you noticed).

As far as I can understand the reason, this is because when you put the RecyclerView in the NestedScrollView , it cannot determine the exact height needed for the RecyclerView . What the developer usually assumes for this (in simple words) The height of the RecyclerView should match match_parent as soon as all the above-viewed screens have left the screen . But, unfortunately, this is NOT the case.

It makes the RecyclerView somehow wrap_content, adding all its views and then measuring its height (correct me if I'm wrong). Not sure about the possible error or expected behavior, but I believe that NestedScrollView should be able to handle this case explicitly, otherwise adding a RecyclerView to NestedScrollView absolutely useless, since it does not process the views, completely destroying the RecyclerView and therefore consumes a lot of memory.

Temporary solution:

Just remove the RecyclerView from the NestedScrollView so that it can properly reuse the views.

NOTE. The answer may not be 100% right, because it is completely based on my personal observations and experience. Any better solution or improvement in response is appreciated.

+4
Nov 09 '16 at 18:58
source share



All Articles