漂亮的Adapter模式-体会RecyclerView的设计实现

来源:互联网 发布:淘宝api 编辑:程序博客网 时间:2024/06/08 00:12

最近在研究设计模式的时候看到了Adapter模式,第一时间就想到了RecyclerView用到的Adapter,简单地走了一遍ReyclerView相关的源码,不得不感叹:设计得真的漂亮。
本文算不上源码分析,只能算是理解设计模式的初级内容。

1.整体把握

平时使用RecyclerView的时候大只可分为三个部分:
1.Adapter
2.LayoutManager
3.RcyclerView
将这三个部分组合在一起就构成了一个漂亮的「多视图展示」的View,作为这么一个优秀的控件,整体的显示结构可以用下图表示:
结构
从右往左看,假设对与不同类型的Layout以及不同格式的数据,通过一个Adapter适配成为一个个ViewHolder,ViewHolder中缓存有每个用于显示的ItemView。ItemView的布局操作则交给了LanyoutManager来管理,ItemView可以根据LayoutManager中的布局策略,完成自己的布局操作,如果不想用系统提供的那三种LayoutManager,完全可以自己根据需求来定制一个,通过Adapter和LayoutManger,整个RecyclerView的功能变得十分强大,可定制性超级高。

2.跟进源码

说到底,RcyclerView终究只是一个ViewGroup,就从它的的onMeasure方法开始简单跟进一下,捋一捋LayoutManager和Adapter的使用时机,这样在以后的定制过程中会有更深的理解。
定位到onMeasure方法,先把主线拎出来,如下图:
方法调用链
从整个方法的调用链可以看出:在onMeasure开始执行的时候,就将measure操作委托给了LayoutManager。

@Overrideprotected void onMeasure(int widthSpec, int heightSpec) {    if (mLayout == null) {        // 如果没有设置LayoutManager,执行默认的测量操作        defaultOnMeasure(widthSpec, heightSpec);        return;    }    //如果设置了LayoutManager,先判断是否开启了自动测量    if (mLayout.mAutoMeasure) {        //开启了自动测量,根据ItemView所占大小,设置RecyclerView        //将测量的操作委托给LayoutManager(mLayout就是LayoutManager的实例)        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);        ……    } else {        //没有开启自动测量        //如果设置了固定的大小则直接将测量的操作委托给LayoutManager        if (mHasFixedSize) {                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);                return;        }       //如果没有设置固定大小,执行自定义的测量流程        ……    }}

系统提供的几种layoutManager都有开启自动测量的功能,即LayoutManager会更具ItemView的大小,自动测量RecyclerView的宽高,具体算法我就没去深入了。
测绘完了后,LayoutManger就会给ItemView进行布局操作了,跳到onLayoutChildren方法中,这个方法的代码比较多,有200行左右,开头的注释描述了具体的布局算法,忽略掉相关的判断操作,最终都调用了一个fill()方法来实现来填充ItemView。

    @Override    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {        // layout algorithm:        // 1) by checking children and other variables, find an anchor coordinate and an anchor        //  item position.        // 2) fill towards start, stacking from bottom        // 3) fill towards end, stacking from top        // 4) scroll to fulfill requirements like stack from bottom.                ····       if (mAnchorInfo.mLayoutFromEnd) {            // fill towards start           ···           fill(recycler, mLayoutState, state, false);           ····            // fill towards end           ····                      fill(recycler, mLayoutState, state, false);            ····        } else {           ····                }        ····    }

由于我真是抱着学习RecyclerView工作大致流程的心态去分析,也就没深入各种逻辑细节了,直接看到fill()方法。

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,            RecyclerView.State state, boolean stopOnFocusable) {       ···        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {            ···            //迭代布局ItemView            layoutChunk(recycler, state, layoutState, layoutChunkResult);            ···                    }        ···    }

看到循环了,顿时就松了一口气,大概也知道具体布局的ItemView的工作多半都是在循环中迭代完成的。

 void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,            LayoutState layoutState, LayoutChunkResult result) {            View view = layoutState.next(recycler);      ···        // We calculate everything with View's bounding box (which includes decor and margins)        // To calculate correct layout position, we subtract margins.        layoutDecoratedWithMargins(view, left, top, right, bottom);     ···}

走到这一步了,注释也说得过去很明白了,计算得到的数据最终会传入layoutDecoratedWithMargins()方法来完成布局,再点进这个方法:

  public void layoutDecoratedWithMargins(View child, int left, int top, int right,                int bottom) {            final LayoutParams lp = (LayoutParams) child.getLayoutParams();            final Rect insets = lp.mDecorInsets;            child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,                    right - insets.right - lp.rightMargin,                    bottom - insets.bottom - lp.bottomMargin);}

终于看到了朝思暮想的layout方法,也走到了ItemView布局的终点。
到此为止,LayoutManager的主线已经被拎出来了,下面就回退到layoutChunk() 中,不可以忽略第一句话View view = layoutState.next(recycler);, 这里获取View的方法尤为重要,应为View的来源是ViewHolder,而Adapter又管理者ViewHolder,自然而然Adapter的调用时机就在这里:

View next(RecyclerView.Recycler recycler) {     if (mScrapList != null) {         return nextViewFromScrapList();     }     final View view = recycler.getViewForPosition(mCurrentPosition);     mCurrentPosition += mItemDirection;     return view;}

走到这,recycler出现了,他是Recycler的实例,也是负责复用与管理ItemView的类。在这个类里面可以轻松找到三个缓存ViewHolder的集合:

final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();ArrayList<ViewHolder> mChangedScrap = null;final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

具体的复用缓存机制就不做深入了,点进getViewForPosition():

View getViewForPosition(int position, boolean dryRun) {     return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;}

发现调用的其实是:tryGetViewHolderForPositionByDeadline():

ViewHolder tryGetViewHolderForPositionByDeadline(int position,                boolean dryRun, long deadlineNs) {                   ···        holder = mAdapter.createViewHolder(RecyclerView.this, type);                   ···        return holder;}

终于看到了熟悉的createViewHolder()方法,这不正是我们实现Adapter时重写的几个方法之一吗?走到这里,Adapter的使用时机大概也知道了,也就完成了分析RecyclerView的目的,就不往下继续走了。很显然ViewHolder的构建需要上层使用者去具体完成,在RecyclerView#Adapter中定义的仅仅是一个抽象的Adapter。
最后总结一下,从设计模式的角度来将,ReyclerView的设计确实十分漂亮,LayoutManager和Adapter各司其职,协同合作,共同实现ReyclerView的功能,并且下层抽象的ViewHodler和Adapter也定义了相关的实现规范,使得上层用户在使用的时候学习成本非常低,并且无需关注优化的细节,不得不说“适配器模式”在RecyclerView的设计中运用的十分合适。

参考资料:RecyclerView源码分析(二)–测量流程

原创粉丝点击