问题描述
我有一个包含370个项目的交错网格,带有图像.
I have a Staggered grid containing 370 items, with images.
我要确保快速回收项目以注意内存,但是会创建ViewHolder,然后将其绑定到适配器中的每个项目,并且不注意子项是否可见
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
我已经尝试了以下
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.
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.
这对我来说可能是一个理解上的问题,但是我认为RecyclerView应该仅绑定那些可见的视图,或者尊重仅20个视图,或者20个池中的视图+不管屏幕上有多少个视图.如何使用StaggeredGridLayoutManager做到这一点?
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?
如果我听滚动更改并使用findFirstCompletelyVisibleItemPositions和findLastCompletelyVisibleItemPositions,那么它仍然会覆盖适配器中的每个项目,而不仅仅是显示在屏幕上的6个项目
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
我的适配器代码
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>
推荐答案
问题:
面临此问题的原因是因为您在NestedScrollView
中添加了RecyclerView
.
这不是我第一次听说此问题,我以及可能所有尝试将RecyclerView
放入NestedScrollView
的人都遇到了此问题(如果注意到的话).
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).
据我所知,这是因为将RecyclerView
放在NestedScrollView
中时,它无法确定RecyclerView
所需的确切高度.通常,开发人员对此的假设(简单来说)是一旦所有上述视图都离开屏幕,RecyclerView的高度应为match_parent .但不幸的是,事实并非如此.
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.
它使RecyclerView
以某种方式wrap_content添加其所有视图,然后测量其高度(如果我错了,请纠正我).不确定可能的错误或预期的行为,但我相信NestedScrollView
应该能够显式处理这种情况,否则,在NestedScrollView
中添加RecyclerView
是完全没有用的,因为它不会回收视图,从而完全破坏了概念,因此会占用大量内存.
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.
只需从NestedScrollView
中删除RecyclerView
,以便它可以正确地重用视图.
Just remove the RecyclerView
from NestedScrollView
so that it can properly reuse the views.
注意:答案可能不是100%正确,因为它完全基于我的个人观察和经验.任何更好的解决方案或答案的改进都将受到赞赏.
这篇关于为什么我的StaggeredGrid RecyclerView布局每个项目,而不仅是可见项目?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!