为什么我的 StaggeredGrid RecyclerView 布局每个项目,而不仅仅是可见的?
Why does my StaggeredGrid RecyclerView layout every item, not only visible ones?
我有一个包含 370 个项目和图像的交错网格。
我想确保快速回收项目以注意内存,但是创建了一个 ViewHolder 然后绑定到我的适配器中的每个项目并且不注意 children 是否可见
我试过以下方法
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 次。然后在恢复对 onCreateViewHolder 的调用之前调用 onViewRecycled 185 次,直到达到完整的 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
}
}
我的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>
片段布局
<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
的人都遇到过这个问题(如果注意到的话)。
据我所知,这是因为当你将RecyclerView
放在NestedScrollView
中时,无法确定RecyclerView
所需的确切高度。通常开发人员对此假设(简单来说)是 RecyclerView 高度应该是 match_parent 一旦所有上述视图都离开屏幕 。但不幸的是,不是这种情况。
它使 RecyclerView
以某种方式 wrap_content 添加其所有视图,然后测量其高度(如果我错了请纠正我)。不确定可能的错误或预期的行为,但我相信 NestedScrollView
应该能够明确处理这种情况,否则,在 NestedScrollView
中添加 RecyclerView
是完全无用的,因为它不会回收视图,完全破坏了RecyclerView
的概念,从而消耗了大量的内存。
临时解决方案:
只需从 NestedScrollView
中删除 RecyclerView
,以便它可以正确重用视图。
注意:答案可能不是 100% 正确,因为它完全基于我个人的观察和经验。任何更好的解决方案或对答案的改进表示赞赏。
问题在于 NestedScrollview
recyclerview
可用的 space 未确定。
您可以使用 android:fillViewport="true"
使 NestedScrollView
测量 RecyclerView
。 RecyclerView
将填充剩余的高度。所以如果你想滚动 NestScrollView
,你可以设置 RecyclerView 的 minHeight。
我有一个包含 370 个项目和图像的交错网格。
我想确保快速回收项目以注意内存,但是创建了一个 ViewHolder 然后绑定到我的适配器中的每个项目并且不注意 children 是否可见
我试过以下方法
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 次。然后在恢复对 onCreateViewHolder 的调用之前调用 onViewRecycled 185 次,直到达到完整的 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
}
}
我的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>
片段布局
<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
的人都遇到过这个问题(如果注意到的话)。
据我所知,这是因为当你将RecyclerView
放在NestedScrollView
中时,无法确定RecyclerView
所需的确切高度。通常开发人员对此假设(简单来说)是 RecyclerView 高度应该是 match_parent 一旦所有上述视图都离开屏幕 。但不幸的是,不是这种情况。
它使 RecyclerView
以某种方式 wrap_content 添加其所有视图,然后测量其高度(如果我错了请纠正我)。不确定可能的错误或预期的行为,但我相信 NestedScrollView
应该能够明确处理这种情况,否则,在 NestedScrollView
中添加 RecyclerView
是完全无用的,因为它不会回收视图,完全破坏了RecyclerView
的概念,从而消耗了大量的内存。
临时解决方案:
只需从 NestedScrollView
中删除 RecyclerView
,以便它可以正确重用视图。
注意:答案可能不是 100% 正确,因为它完全基于我个人的观察和经验。任何更好的解决方案或对答案的改进表示赞赏。
问题在于 NestedScrollview
recyclerview
可用的 space 未确定。
您可以使用 android:fillViewport="true"
使 NestedScrollView
测量 RecyclerView
。 RecyclerView
将填充剩余的高度。所以如果你想滚动 NestScrollView
,你可以设置 RecyclerView 的 minHeight。