Android recycleview

写出你自己的 RecyclerView LayoutManage

2015-06-06  本文已影响4998人  大飛機

原文链接
如果你是Android开发者,到现在为止,你应该至少听说过RecyclerView;一个新的组件,将被加入Support库,它致力于便捷自定义、高性能、可回收的View集合。关于RecyclerView怎么使用的文章在网上已经有一大片就不多说了,这里有几个Demo:
A First Glance at Android's RecyclerView
The new TwoWayView
RecyclerViewItemAnimators
在这一系列的文章中,我将关注于底层的实现,来帮助你建立你自己的较复杂LayoutManager实现,而不是一个简单的横向或者竖向的滑动列表。

在介绍之前,需要注意一下,LayoutManager API 支持强大且复杂的布局回收,因为它没有实现很多,大部分的代码需要你自己去实现。就像在其他的项目中所涉及的自定义View,不要过度的封装和过度的优化你的代码,只需实现在这个应用需要的特性即可。

RecyclerView Playground

在该系列的文章中,所有的代码片段都是来自于我之前在GitHub中提交的代码RecyclerView Playground sample,这个示例包含了几乎各种类型的RecyclerView的用法,从创建一个简单的列表到自定义LayoutManagers。
这篇文章中的代码是从 FixedGridLayoutManager的示例中摘出来的,一个二维的Grid Layout,并且支持水平和垂直方向的滑动。

The Recycler

首先,看一下API的结构。LayoutManager被赋予一个可访问的Recycler实例在需要的地方,当你需要回收旧的views时,或者需要从可能之前已经被回收的之前的子view中获取新的views。

Recycler同时屏蔽了直接操作当前Adapter中的views,当你的LayoutManager需要请求一个新的子view是,只需要简单的调用getViewForPosition(),然后Recycler将会把绑定正确数据的View返回给你。Recycler注重确定是否需要创建一个新的View还是将已存在的,报废的View复用。在LayoutManager中,你的职责就是确定那些不再显示的View及时传递给Recycler,这将使Recycler保证不会创造多于需求的View。

Detach vs.Remove

有两种方式可以在更新布局的适合传递退出的子View,分离和移除。分离是一种比较轻量级的重新排序的方式。在你的代码被返回之前,被分离的View将被重新附加,这将被用于改变附加子View指数而不需要通过Recycler重新绑定或者重新创建这些View。

移除意味着View将不在需要,任何被永久移除的将被放置在Recycler用于以后的复用,但是API没有强制这样做,这取决于你是否需要回收它们。

Scrap vs. Recycle

Recycler是两级缓存系统:废料堆和回收池。废料堆是一个轻量级的可回收的集合,这里的View可以被直接返回到LayoutManager而不需要经过Adapter。
View通常被放在这里当它们被暂时废弃的时候,但在同一次布局中马上复用。回收池由一些被认定为绑定不正确的数据(从其他位置类的数据)的View,所以在传递到LayoutManager之前,它们将被传递到Adapter去重新绑定。

当尝试着给LayoutManager提供新的View时,Recycler首先会检查废料堆去匹配位置/ID,如果存在,它将被直接返回,而不需要重新通过Adapter绑定数据。如果没有匹配的View,Recycler将会从回收池中取一个合适的View并通过Adapter绑定必要的数据(i.e. RecyclerView.Adapter.bindViewHolder() 会被执行)在返回之前。万一回收池中没有可用的View,一个新的View将会被创建(i.e. RecyclerView.Adapter.createViewHolder()将会被执行)在绑定之前,然后返回。

Rule of Thumb

如果你愿意,LayoutManager API可以让你独立做很多完美的事情,所以可能的组合是多种多样的,一般来说,对你想要临时改编并期望在同一操作中使用的可以直接调用detachAndScrapView(),如果你不需要马上复用,则可以调用removeAndRecyclerView()

Building The Core

LayoutManager实例主要负责在需要的时候,实时的添加、测量和铺设所有的子View,当用户在滑动View的时候,它将取决于LayoutManager去决定什么时候,子View需要被添加,什么时候旧的View可以被分离和报废。

你将需要重写和继承以下的方法,去创建一个最小的可用的LayoutManager.

generateDefaultLayoutParams()

事实上,这个方法是唯一一个必须被重写的方法。实现非常的简单,只要返回一个RecyclerView.LayoutParams的实例用于设置子View的高度,在返回getViewForPosition()之前,这些参数将被赋予每个子View。

@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams(){
    return new RecyclerView.LayoutParams(
        RecyclerView.LayoutParams.WRAP_CONTENT,
        RecyclerView.LayoutParams.WRAP_CONTENT);
}

onLayoutChildren()

这是LayoutManager的主要入口,当view需要被初始化或者Adapter的数据有变化时会调用该方法。不管是初始化还是数据源变化,对于重置子view,这是个好时机。

在下段,我们将学习当Adapter更新时,这个方法如何实现基于当前显示的元素布局。现在,我们简单的认为它是实现初始化子布局。底下是一个简单的例子,在FixedGridLayoutManager示例:

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    //报废的view,用于计算宽高
    View scrap = recycler.getViewForPosition(0);
    addView(scrap);
    measureChildWithMargins(scrap, 0, 0);

    /*
     * 在这里我们假设每个Item的高度是一致的
     * 这使我们可以计算Item的大小,因为Item的大小是不变的
     */
    mDecoratedChildWidth = getDecoratedMeasuredWidth(scrap);
    mDecoratedChildHeight = getDecoratedMeasuredHeight(scrap);
    detachAndScrapView(scrap, recycler);

    updateWindowSizing();
    int childLeft;
    int childTop;

    /*
     * 重置第一个item的位置
     */
    mFirstVisiblePosition = 0;
    childLeft = childTop = 0;

    //Clear all attached views into the recycle bin
    detachAndScrapAttachedViews(recycler);
    //Fill the grid for the initial layout of views
    fillGrid(DIRECTION_NONE, childLeft, childTop, recycler);
}

我们设定并保证所有view的退出都保持到废料堆里,我们抽象了一部分工作到fillGrid()以便于复用,不久之后我们将看到这个方法中执行了一大堆的更新view是否显示当滑动发生时。

就像是对ViewGroup的自定义实例化,你的任务就是触发测量布局每个从Recycler中获取到的子view。这些工作Api是无法直接实现的。

总体来说,初步完成这个方法需要执行的步骤如下:

注意我们在讨论的FixedGridLayoutManager.fillGrid()。这个Manager调整从右到左的位置,当到达最大列时,包裹他们:

by goyourfly


未完待续...

上一篇下一篇

猜你喜欢

热点阅读