为什么我的StaggeredGrid RecyclerView布局每个项目,而不仅是可​​见项目? [英] Why does my StaggeredGrid RecyclerView layout every item, not only visible ones?

查看:99
本文介绍了为什么我的StaggeredGrid RecyclerView布局每个项目,而不仅是可​​见项目?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个包含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布局每个项目,而不仅是可​​见项目?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆