ListView 源码分析(一)setAdapter 方法实现

对于一个初学者来说,最需要掌握的技能之一是 ListView 加载布局,不管是同一种布局还是不同的布局,又或者是加载头文件或者加载底部文件。这些掌握起来不是很难,而对于一个中级的搬砖的来说,这些就太过于肤浅了,如果不懂他的原理,那你就只能在一个叫做万恶的 Adapter 上各种吃亏了。那么请允许我来带你揭开它的神秘面纱,能力一般,水平有限,有不对的请告诉我。

ListView 的用法以及为什么 ListView 要这么设计,要使用一个 Adapter 来展示布局,就不细说了,总之一句话,就是为了更好地拓展。

废话少说,进入正题,直接从 setAdapter 方法分析,上源码:

    @Override
    public void setAdapter(ListAdapter adapter) {
        //清空数据操作
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        ...

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
            checkFocus();

            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());

            int position;
            if (mStackFromBottom) {
                position = lookForSelectablePosition(mItemCount - 1, false);
            } else {
                position = lookForSelectablePosition(0, true);
            }
            setSelectedPositionInt(position);
            setNextSelectedPositionInt(position);

            if (mItemCount == 0) {
                // Nothing selected
                checkSelectionChanged();
            }
        } else {
            mAreAllItemsSelectable = true;
            checkFocus();
            // Nothing selected
            checkSelectionChanged();
        }

        requestLayout();
    }

省略了一部分,省略号上边的主要是清空数据的操作,还有我们发现源码 ListView 设置的 Adapter 是一个观察者模式啊,我们继续往下看,其实关键代码只有 10 - 13 行 和第 19 行 还有第 41 行代码,下边一一说明:

(1) mOldItemCount:数据改变前的item数量

(2) mItemCount 所有item的数量

(3) mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());  mAdapter.getViewTypeCount() 是不是很眼熟,没错,就是设置 View 显示类型的

(4) requestLayout 方法,就是我们要继续往下看的方法了,他便是绘制 ListView 视图的方法。 

我们继续往下看源码,接来下看 requestLayout 源码,点击进入这个方法,我们发现这个方法实际上是在 AbsListView 中的犯法,而最终调的确是 View 中的方法,源码如下:

@CallSuper
public void requestLayout() {
    if (mMeasureCache != null) mMeasureCache.clear();

    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        // Only trigger request-during-layout logic if this is the view requesting it,
        // not the views in its parent hierarchy
        //如果当前的整个View树在进行布局流程的话,则会调用requestLayoutDuringLayout()
        //让这次的布局延时执行  反正我是没理解
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null && viewRoot.isInLayout()) {
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }

   //PFLAG_FORCE_LAYOUT会在执行View的measure()和layout()方法时判断
    //只有设置过该标志位,才会执行measure()和layout()流程
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;
    //自己布局  则调用父类的requestLayout 开始布局
    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

该方法主要是设置了 PFLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED 到当前 View 的 Flag 中,这个过程,就是设置当前View 标志位后,就不断的向上调用父布局 View 的 requestLayout(),最后调用到根 View 即 DecorView 的 requestLayout()。然后调用父 View 的 requestLayout 方法,但是我们进入父 View (ViewParent)  发现他就是一个接口,没有具体的实现啊,所以我们需要去找它的实现类,ViewParent 的实现类是 ViewRootImpl,我们来看下 ViewRootImpl 中 requestLayout 的代码:

@Override
public void requestLayout() {
  //该boolean变量会在ViewRootImpl.performLayout()开始时置为ture,结束置false
    //表示当前不处于Layout过程
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

如果当前没有进行 layout 过程,则会调用 scheduleTraversals() 方法,进入ViewRootImpl.scheduleTraversals()。

   void scheduleTraversals() {
        if (!mTraversalScheduled) {
       //在下一段代码处会置回false
       //表示在排好这次绘制请求前,不再排其它的绘制请求
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 我们来解读下这行代码,我们发现在这里调用了一个重要的变量 mChoreographer,它是 Choreographer 类型的,这个对象会请求 Vsync 信号来控制绘制的进行,实现了按帧进行绘制的机制。该方法对于绘制的请求经过了 Choreographer 的编排后,最终会调用回 ViewRootImpl.doTraversal() 方法。

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); 

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

根据源码我们发现最后调用的是 performTraversals 方法,自此 ListView 的 measure、layout 和 draw 过程就开始了。

本文只是解读 setAdapter 方法,至于  ListView 的绘制过程和 recycleBin 机制会在后文解答。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值