Android RecyclerView之RecycledViewPool、SortedListAdapter

想必Tabs+ViewPager+ListView 结合使用的场景在你的Android手机中的各大应用里并不少见,比如最为典型的网易新闻。

众所周知,用RecyclerView可以非常简单的替代掉ListView。可仅仅就为了将ListView换成RecyclerView,这换汤不换药的做法显然不足以让人心动。

如果我说,再用上RecycledViewPool,可以使你的布局渲染速度、ViewPager滑动流畅度上升一个档次,你会心动并行动吗?

RecycledViewPool是什么?

关于RecycledViewPool,官方文档是这样说的:

Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. This can be useful if you have multiple RecyclerViews with adapters that use the same view types, for example if you have several data sets with the same kinds of item views displayed by a ViewPager.

RecyclerView automatically creates a pool for itself if you don’t provide one.

简言之就是,你可以给RecyclerView设置一个ViewHolder的对象池,这个池称为RecycledViewPool,这个对象池可以节省你创建ViewHolder的开销,更能避免GC。即便你不给它设置,它也会自己创建一个。

如此说来,如果多个RecylerView间共用一个RecycledViewPool是不是能让你的UI更加的“顺滑”?

使用RecycledViewPool

RecycledViewPool使用起来也是非常的简单:先从某个RecyclerView对象中获得它创建的RecycledViewPool对象,或者是自己实现一个RecycledViewPool对象,然后设置个接下来创建的每一个RecyclerView即可。

需要注意的是,如果你使用的LayoutManager是LinearLayoutManager或其子类(如GridLayoutManager),需要手动开启这个特性:

layout.setRecycleChildrenOnDetach(true)

RecyclerView view1 = new RecyclerView(context);
LinearLayoutManager layout = new LinearLayoutManager(context);
layout.setRecycleChildrenOnDetach(true);
view1.setLayoutManager(layout);
RecycledViewPool pool = view1.getRecycledViewPool();
//...

RecyclerView view2 = new RecyclerView(context);
//... (set layout manager)
view2.setRecycledViewPool(pool);

//...

RecyclerView view3 = new RecyclerView(context);
//...(set layout manager)
view3.setRecycledViewPool(pool);

ViewPager中使用原理同上,只是你得通过ViewPagerAdapter传递个下一个RecyclerView。

代码示例如下:

public class PagerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pager);
        ViewPager pager = (ViewPager) findViewById(R.id.pager);
        pager.setAdapter(new PageAdapter(getSupportFragmentManager()));
    }

    static class PageAdapter extends FragmentPagerAdapter{
        RecyclerView.RecycledViewPool mPool = new RecyclerView.RecycledViewPool();
        public PageAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int i) {
            RecyclerViewFragment f = new RecyclerViewFragment();
            f.mPool = mPool;
            return f;
        }

        // ...
    }

    public static class RecyclerViewFragment extends Fragment{
        RecyclerView.RecycledViewPool mPool;
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            RecyclerView view = new RecyclerView(inflater.getContext());
            LinearLayoutManager layout = new LinearLayoutManager(inflater.getContext());
            layout.setRecycleChildrenOnDetach(true);
            view.setLayoutManager(layout);
            if (mPool != null) {
                view.setRecycledViewPool(mPool);
            }
            view.setAdapter(...);
            return view;
        }
        // ...
    }
}

布局activity_pager.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v4.view.PagerTitleStrip
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </android.support.v4.view.ViewPager>
</LinearLayout>

详见 https://gist.github.com/yrom/728237025575005a8fd3

More Tips

  1. RecycledViewPool是依据ItemViewType来索引ViewHolder的,所以你必须确保共享的RecyclerView的Adapter是同一个,或view type 是不会冲突的。

  2. RecycledViewPool可以自主控制需要缓存的ViewHolder数量: mPool.setMaxRecycledViews(itemViewType, number);毕竟池子里的水并不是越深越好。

  3. RecyclerView可以设置自己所需要的ViewHolder数量,只有超过这个数量的detached ViewHolder才会丢进ViewPool中与别的RecyclerView共享。 recyclerView.setItemViewCacheSize(10);

  4. 在合适的时机,RecycledViewPool会自我清除掉所持有的ViewHolder对象引用,不用担心池子会“侧漏”。当然你也可以在你认为合适的时机手动调用clear()

如何用RecyclerView快速实现列表页面。

如一个简单的列表场景:TodoList。

分页加载现有Todo
现有数据基础上增、删、改

RecyclerView的使用在此就不赘述了,本文主要讨论RecyclerView.Adapter的实现

使用最简单的ArrayList实现,如下:

class ListAdapter extends RecyclerView.Adapter<TodoViewHolder> {
    final ArrayList<Item> mData;
    final LayoutInflater mLayoutInflater;
    public SortedListAdapter(Context context) {
        mLayoutInflater = LayoutInflater.from(context);
        mData = new ArrayList<>();
    }

    public void addItem(Item item) {
        mData.add(item); // 需要自己通知更新
    }

    @Override
    public TodoViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
        return new TodoViewHolder (
                mLayoutInflater.inflate(R.layout.list_todo_item, parent, false));
    }

    @Override
    public void onBindViewHolder(TodoViewHolder holder, int position) {
        holder.bindTo(mData.get(position));
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }
}

这样的Adapter一个显而易见的问题就是,如何做数据的去重

  1. 添加一项数据:最简单的是在addItem()之前,遍历一次mData,定位后再决定是插入还是更新现有数据,并调用notifyItemInserted(pos)
  2. 添加多个数据:多次重复上面的方法…

对于少量数据来说这样做并不见得有什么问题,而且写得多了,都有自己封装好的诸如ArrayObjectAdapter之类方便使用。

这样就够了吗?

答案肯定是不。Android Support Library 悄悄给我们提供了一个叫SortedList的工具类,它默默的藏在support库的角落中,鲜为人知。

SortedList?

文档对它的定义:

  1. 是一个有序列表
  2. 数据变动会触发回调SortedList.Callback的方法,如onChanged()

构造一个SortedList需要实现它的回调SortedList.Callback,并由其来定义数据的排序和数据的唯一性。
它有一个实现类SortedListAdapterCallback就是RecyclerView.AdapterSortedList交互的秘密武器。

示例

改造后的ListAdapter:

class SortedListAdapter extends RecyclerView.Adapter<TodoViewHolder> {
    final SortedList<Item> mData;
    final LayoutInflater mLayoutInflater;
    public SortedListAdapter(Context context) {
        mLayoutInflater = LayoutInflater.from(context);
        mData = new SortedList<Item>(Item.class, new SortedListAdapterCallback<Item>(this){
            @Override
            public int compare(Item t0, Item t1) {
                // 实现这个方法来定义Item的显示顺序
                int txtComp = t0.mText.compareTo(t1.mText);
                if (txtComp != 0) {
                    return txtComp;
                }
                if (t0.id < t1.id) {
                    return -1;
                } else if (t0.id > t1.id) {
                    return 1;
                }
                return 0;
            }

            @Override
            public boolean areContentsTheSame(Item oldItem,
                    Item newItem) {
                // 比较两个Item的内容是否一致,如不一致则会调用adapter的notifyItemChanged()
                return oldItem.mText.equals(newItem.mText);
            }

            @Override
            public boolean areItemsTheSame(Item item1, Item item2) {
                // 两个Item是不是同一个东西,
                // 它们的内容或许不一样,但id相同代表就是同一个
                return item1.id == item2.id;
            }
        });
    }

    public void addItem(Item item) {
        mData.add(item);
        // 会通过SortedListAdapterCallback自动通知更新
    }

    ...

    @Override
    public int getItemCount() {
        return mData.size();
    }
}

虽然相对ListAdapter代码量变多了,但是调用者却再也不用关心数据的去重与通知更新的问题。这一切都有SortedListAdapterCallback帮你自动处理好了。

单单只这一个好处其实并不值得劳师动众去改掉现有Adapter使用SortedList,但它另外还有一个令人称赞并喜爱的功能:批量更新(Batched Updates)

就如实现添加多个数据:

void addItems(List<Item> items){
    mData.beginBatchedUpdates();  // 开始批量更新
    mData.addAll(items);          // 更新一批数据
    mData.endBatchedUpdates();    // 结束更新
}

批量删除:

void deleteItems(List<Item> items){
    mData.beginBatchedUpdates();  // 开始批量更新
    for(Item item : items){       // 删除一批数据
         mData.remove(item);
    }
    mData.endBatchedUpdates();    // 结束更新
}

等等。

如例子所示,调用beginBatchedUpdates()之后,所有的对SortedList操作都会等到endBatchedUpdates()之后一起生效。

完整的示例见:官方Support库Samples ($ANDROID_SDK/extras/android/support/samples/Support7Demos)

More Tips

  1. 列表无序的情况,可以用其id或原始数据List的index来比较排序,只要确保能正确实现compare即可
  2. 如无需批量更新,或无频繁的增删改,其实用前面的ListAdapter比较好。

总之:如果你的列表需要批量更新或者频繁删改,且刚好有明确的先后顺序,快使用SortedList

文章来自:http://www.yrom.net/blog/2015/05/30/recyclerView-tips-1-recycledviewpool/

延伸:

新的工具类SortedListAdapterCallback,配合RecyclerView、Adapter和SortedList一起使用更加方便的管理我们在Adapter中的数据,省去我们很多“多余“的工作。

使用方法

  • 申明一个SortedList类型的数据聚合
     SortedList<Object> mDataList;
  • 实现SortedListAdapterCallback抽象类
    static class ObjectListCallback extends SortedListAdapterCallback<Object>{

        public ObjectListCallback(RecyclerView.Adapter adapter) {
            super(adapter);
        }

        @Override
        public int compare(Object o1, Object o2) {
            //TODO:
            return 0;
        }

        @Override
        public boolean areContentsTheSame(Object oldItem, Object newItem) {
            //TODO:
            return false;
        }

        @Override
        public boolean areItemsTheSame(Object item1, Object item2) {
            //TODO:
            return 0;
        }
    }

其中compare()函数是判断SortedList中数据的现实顺序的。而areContentsTheSame()和areItemsTheSame()这两个抽象方法初看好像差不多(~.~),但其实区别还是很大的,areContentsTheSame()是取代了equals方法,用于判断SortedList中Item是否改变,如果改变则调用onChanged()函数。 而areItemsTheSame()则是用于判断SortedList中两个Item是否代表相通对象,其实就是数据的去重。

  • 创建一个RecyclerView Adapter,并进行SortedList的初始化。
    mDataList = new SortedList<>(Object.class, new ObjectListCallback(mAdapter));
  • 当数据有发生改变时,例如删除,增加等,你只需直接对mDataList进行相应操作,再无需关心mAdapter内数据显示更新问题,例如notifyDataChanged等通知函数的调用,因为SortedListAdapterCallback内的回调函数会自动帮你完成。
    mDataList.beginBatchedUpdates();
    mDataList.addAll(object);
    mDataList.endBatchedUpdates();

这样就简单地完成了数据的加载,RecyclerView会自动刷新UI。

局限性

SortedListAdapterCallback目前仅支持与RecyclerView Adapter搭配使用,并不支持ListView。

结语

第一次见到SortedListAdapterCallback这个工具类时,第一感觉就是眼前一亮,因为它和我们一般认知的Adapter与DataLis间的依赖关系完全相反,它是将Adapter直接“塞入“到DataList中并把细节都进行了封装,这样我们就只要关心DataList中的数据更新问题,而无需关心RecyclerView的各种刷新,哈哈,真的是非常方便,非常建议大家使用。


    原文作者:一叶飘舟
    原文地址: https://blog.csdn.net/jdsjlzx/article/details/51121090
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞