RecyclerView与ListView对比浅析(一):初始化篇

ListView是Android中常用的控件之一,也是信息流中主体展示的重要控件,它在复用性、流畅性及易用性上均有着较好的表现。Google在V7支持包中推出了新控件RecyclerView,它封装了处理convertView缓存的部分,开发者只需要实现View的创建和更新两个部分就可实现列表,另外它还可以用LayoutManager实现不同的布局,比如List和Grid。最近Facebook的首页信息流也使用了这一控件,但从拉View树的结果看,貌似是和ListView切换使用的。

下面就在源码层面,从初始化、缓存机制和绘制机制上分三部分对比下两种View,欢迎大家指正。

(ListView的源码版本采用的是Android 4.4,为目前最主流的版本;RecyclerView则是用的最新v7包里的)

(一)初始化篇

1.  AbsListView(源码版本4.4)

ListView的父类是AbsListView,ListView中的View缓存和Adapter等主要机制均继承自AbsListView。

1.1    初始化

这一部分主要在构造函数中实现,主要分三部分:

(1)      AbsListView的初始化initAbsListView(),主要是对View的Clickable、Focusable、Cache等熟悉进行初始化,同时拿到ViewConfiguration初始化TouchSlop(触摸溢出)、Velocity(滑动速度)等变量。

(2)      ScrollBar的初始化,在Context中拿到View的Style,调initializeScrollbars(a)进行初始化,这是View里的函数。

(3)      AbsListView风格设置,从Context拿到AbsListView的TypedArray,对其专属的风格进行设置,包括scrollingCache、transcriptMode、fastScroll、choiceMode等。

1.2    Adapter

ListView的适配器,基类Adapter是个接口,需要开发者继承实现自己的Adapter,主要有以下几个函数需要实现:

(1)      getView(int position, View convertView, ViewGroup parent):这是处理缓存View和更新View的主要函数,position是当前需要处理的item项的位置,convertView是外面传进来的缓存View,parent是父View

(2)      hasStableIds():可自己实现,如果item id永远是与对象的一一对应的,就返回true,这个函数在寻找缓存View时会用到,如果是true,就可以在Adapter里找缓存View

(3)      getItem、getCount、getItemId等get函数

(4)      getItemViewType(int position):获取相应position上的ViewType,这个ViewType会在getView的时候确定,两个可以互相转换的View归作一类,这种就可以重用

(5)      registerDataSetObserver/unregister,设置一个观察者,当Adapter里的Data发生变化,就执行相应操作。

2   ListView(源码版本4.4)

    ListView的初始化在父类的基础上增加了Divider(分割线)、OverScrollHeader和OverScrollFooter的设置。


3 RecyclerView(源码版本5.1.1)

LayoutParams

RecyclerView自定义的LayoutParams,里面可以存储mViewHolder,表示该View所属的ViewHolder,通过getChildViewHolder(view),可以拿到view的ViewHolder,很常用

 

3.1初始化

RecyclerView会做以下几个初始化动作:

   (1)mChildHelper的初始化,首先介绍下这个ChildHelper类,它会协助获取RecyclerView中的childVIew,并提供忽略隐藏Children的功能,也就是说,调它的getChildAt只会在当前显示的Children中去查找,如果想查HiddenChildren,需要调getUnfilteredChildAt。

   (2)mItemAnimator的初始化,处理各Item的动画

   (3)mAdapterHelper的初始化,其中封装了处理Adapter更新的操作。

3.2几个重要的类

3.2.1.ViewHolder

ViewHolder是RecyclerView进行缓存和处理的基本单位,每一种类型的item需要对应一种ViewHolder。它与ListView中的ViewHolder又有所不同,其中只存储着唯一一个itemView及其相关参数,这个itemView类似于ListView的convertView。有趣的是为了能查到itemView所属的ViewHolder,这里自定义了一个LayoutParams,其所属的ViewHolder作为一个参数存储在里面,然后set到itemView里。

(1)变量:

itemView:ViewHolder中存储的View,为其提供缓存和更新服务

mPosition:itemView当前在RecyclerView中的位置

mOldPosition:itemView在RecyclerView中的旧位置

mItemId:itemView对应的Id,当Adapter的StableID为true时,id与position相等

mItemViewType:itemView对应的ViewType

mScrapContainer:ViewHolder自带的Recycler,存储可重用的ViewHolder,其实存储的就是其所属ViewHolder(this)。

 

函数方法方面大多是get方法,就不细说了

3.2.2.Adapter<VH>

RecyclerView自定义的Adapter,VH表示其对应的ViewHolder类型,可以写个BaseViewHolder作为泛型入参,把多个ViewHolder集合到一个Adapter中。

 

(1)Adapter的变量只有两个:

AdapterDataObservable mObservable:用于监控Adapter的数据变化,有变化就进行相应操作。

Boolean mHasStableIds:沿用了原版Adapter的StableId机制,也就是如果item id永远是与对象的一一对应的,就设为true,此时id等同于position。

 

(2)Adapter是一个虚拟类,需要其子类实现几个核心函数:

public abstract VH onCreateViewHolder(ViewGroupparent, int viewType)

这个方法用于创建ViewHolder,根据viewType创建View,作为入参创建相应的ViewHolder,这里的viewType是通过内部调用getItemViewType(int position)获得的,它默认是返回0,所以在处理多种类型的显示时,需要重写此函数返回相应类型。

public abstract void onBindViewHolder(VH holder, intposition)

当RecyclerView需要展示数据时,就会调用这个函数,其主要功能是对holder中的itemView的数据进行更新。与ListView不同的是,当item的data发生变化时,不会再次调用这个函数,除非item进行invalidate或者无法确定新位置,所以只需要根据position拿到数据使用即可,不用在里面存一份。

public abstract int getItemCount()

返回Item的总数,这个函数的实现与ListView的类似。

3.2.3.LayoutManager

    LayoutManager负责对RecyclerView中的itemView进行measure和定位,同时还决定什么时候回收用户不可见的itemView。目前官方已支持Linear和Grid的LayoutManger。

(1)变量:

    ChildHelper mChildHelper:存储itemView的容器。

    RecyclerView mRecyclerVIew:它所管理的RecyclerView,使用时需set进来。

    SmoothScroller mSmoothScroller:用于追踪目标View的位置和触发计划好的滑动。(可为空)

    Boolean mRequestedSimpleAnimations:是否有简单动画的标志位

 

(2)关键函数:

    public void onLayoutChildren(Recyclerrecycler, State state)

这个和AbsListView的layoutChildren类似,必须由子类进行具体实现,但这个函数并没有被设成abstract,而是在里面写了个Log注明“You must override onLayoutChildren(Recycler recycler, State state)”。

    private void addViewInt(View child, int index, boolean disappearing)

LayoutManager可以自行往RecyclerView里添加View,流程如下:

    a. 首先根据child拿到其所属的ViewHolder。

    b. 根据第三个入参diappearing判断,选择是否加入或移出RecyclerView的DisappearingList。

    c. 检查ViewHolder的isScrap和ReturndFromScrap标志位,也就是检查ViewHolder自带的mScrapContainer,不为空就代表ViewHolder属于缓存的View。如果为true则清理其Scrap状态。然后调mChildHelper.attachViewtoParent将此View与父View(RecyclerView)关联。

    d. 如果不是缓存View,就看(child.getParent()== mRecyclerView),如果是那说明也是合法的View,并且已存在mChildHelper中。然后分别获取其在mChildHelper中的index和mChildHelper的count,如果index!=count,就把这个child移到count位置,也就是队列最后。

e. 如果以上条件都不符合,说明是个全新的View,那就直接调mChildHelper.addView加入。

        private void addViewInt(View child, int index, boolean disappearing) {
            final ViewHolder holder = getChildViewHolderInt(child);
            if (disappearing || holder.isRemoved()) {
                //layout后这些View将会被隐藏
                mRecyclerView.addToDisappearingList(child);
            } else {
                mRecyclerView.removeFromDisappearingList(child);
            }
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            // 这个Holder是Scrap中的缓存Holder
            if (holder.wasReturnedFromScrap() || holder.isScrap()) {
                if (holder.isScrap()) {
                    holder.unScrap();
                } else {
                    holder.clearReturnedFromScrapFlag();
                }
                mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
                if (DISPATCH_TEMP_DETACH) {
                    ViewCompat.dispatchFinishTemporaryDetach(child);
                }
            } else if (child.getParent() == mRecyclerView) { 
                // 可能已经存储在ChildHelper中,修正位置
                int currentIndex = mChildHelper.indexOfChild(child);
                if (index == -1) {
                    index = mChildHelper.getChildCount();
                }
                if (currentIndex == -1) {
                    throw new IllegalStateException("Added View has RecyclerView as parent but"
                            + " view is not a real child. Unfiltered index:"
                            + mRecyclerView.indexOfChild(child));
                }
                if (currentIndex != index) {
                    mRecyclerView.mLayout.moveView(currentIndex, index);
                }
            } else {
                //全新的View,直接add
                mChildHelper.addView(child, index, false);
                lp.mInsetsDirty = true;
                if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
                    mSmoothScroller.onChildAttachedToWindow(child);
                }
            }
            if (lp.mPendingInvalidate) {
                if (DEBUG) {
                    Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder);
                }
                holder.itemView.invalidate();
                lp.mPendingInvalidate = false;
            }
        }

public voidmeasureChild(View child, int widthUsed, int heightUsed)

前面说过,LayoutManager也负责对childView进行measure,这里就看看它是怎么Measure的:

    a. 首先拿到两个东西:child的LayoutParams--lp,调getItemDecorInsetsForChild(child)拿到itemDecoration的Rect—insets。

    b. widthUsed和heightUsed分别加上insets的左右和上下间隔。

    c. 用上面的计算结果作为入参,调getChildMeasureSpec拿到widthSpec和heightSpec,执行child.measure(widthSpec, heightSpec)。


       public void measureChild(View child, int widthUsed, int heightUsed) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
            widthUsed += insets.left + insets.right;
            heightUsed += insets.top + insets.bottom;

            final int widthSpec = getChildMeasureSpec(getWidth(),
                    getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
                    canScrollHorizontally());
            final int heightSpec = getChildMeasureSpec(getHeight(),
                    getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
                    canScrollVertically());
            child.measure(widthSpec, heightSpec);
        }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值