问题
I have a Staggered grid containing 370 items, with images.
I want to make sure the items are recycled quickly to be careful with memory, but a ViewHolder is created and then bound for every single item in my adapter and pays no attention to whether children are visible
I've 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
Logging shows onCreateViewHolder and onBindViewHolder are called 185 times each. Then onViewRecycled is called 185 times before resuming calls to onCreateViewHolder until we reach the full 370.
This could be an understanding problem on my part, but I think the RecyclerView should bind only those view that are visible, or to honor having only 20 views, or 20 in pool + however many fit on screen. How can I make this happen with the StaggeredGridLayoutManager?
If I listen to scroll changes and use findFirstCompletelyVisibleItemPositions, and findLastCompletelyVisibleItemPositions this still spans every single item in the adapter, not just the 6 that fit on 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
}
}
My Activity Layout Structure
<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>
回答1:
Problem:
The reason you are facing this is issue is because you have added RecyclerView
in NestedScrollView
.
Reason:
It's not first time I have heard of this issue, me and probably everyone who has tried to put RecyclerView
in NestedScrollView
has faced this issue (if noticed).
As far as I could figure the reason, it is because when you place RecyclerView
in NestedScrollView
, it is unable to identify exact height required for RecyclerView
. What normally a developer assumes for this (in simple words) is RecyclerView height should be match_parent once all above views have gone off screen. But unfortunately, this is NOT the case.
It makes RecyclerView
somehow wrap_content adding all its views and then measuring its height (correct me if I am wrong). Not sure a possible bug or expected behaviour, but I believe NestedScrollView
should be able to handle this case explicitly, otherwise, adding RecyclerView
in NestedScrollView
is completely useless, as it does not recycle views, completely destroying the RecyclerView
concept and thus consuming a lot of memory.
Temporary Solution:
Just remove the RecyclerView
from NestedScrollView
so that it can properly reuse the views.
NOTE: The answer may not be 100% right as it is based completely on my personal observation and experience. Any better solution or improvements in the answer is appreciated.
回答2:
The issue is in NestedScrollview
the space available to the recyclerview
is not determined.
You can use android:fillViewport="true"
to make NestedScrollView
measure the RecyclerView
. The RecyclerView
will fill the remaining height. so if you want to scroll the NestScrollView
, you can set the RecyclerView's minHeight.
来源:https://stackoverflow.com/questions/40352367/why-does-my-staggeredgrid-recyclerview-layout-every-item-not-only-visible-ones