源码探索系列16---初篇结尾---那个被RecyclerView替代的Listview

作为这源码探索系列的初篇的终结,选择Listview来做最后一个探索的对象好像也挺好的。
所以我们就来简单的看下我们曾经最熟悉的Listview是怎么去绘制我们的各个View,如何复用的View。

起航

API:23

我们的Listview是继承于AbsListView

public class ListView extends AbsListView 

而这个AbsListView里面帮我们做了大量的工作,包括与我们的adapter相关的工作,对View的复用等。

我们就从我们的最熟悉的一句setAdapter()开始吧

@Override
public void setAdapter(ListAdapter adapter) {
    if (mAdapter != null && mDataSetObserver != null) {
        mAdapter.unregisterDataSetObserver(mDataSetObserver);
    }

    resetList();
    mRecycler.clear();

    if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
        mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
    } else {
        mAdapter = adapter;
    }

    mOldSelectedPosition = INVALID_POSITION;
    mOldSelectedRowId = INVALID_ROW_ID;

    // AbsListView#setAdapter will update choice mode states.
    super.setAdapter(adapter);

    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();
}

看惯了几百行一个的函数,现在整个这么长贴起来都不觉得长了。
现在来解释下,开头的这里两句,如原来有adapter的话,就注销掉数据观察者mDataSetObserver

if (mAdapter != null && mDataSetObserver != null) {
    mAdapter.unregisterDataSetObserver(mDataSetObserver);
}

还记得我们每次修改数据后,都去调用下面这句吗?

notifyDataSetChanged();

因为底层在数据变化上使用的是观察者模型来进行解耦操作。

public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }

public class DataSetObservable extends Observable<DataSetObserver> {

    public void notifyChanged() {
        synchronized(mObservers) { 
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

    public void notifyInvalidated() {
        synchronized (mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onInvalidated();
            }
        }
    }
}

在我们的AbsListview定义了这个DataSetObserver

class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
    @Override
    public void onChanged() {
        super.onChanged();
        if (mFastScroll != null) {
            mFastScroll.onSectionsChanged();
        }
    }

    @Override
    public void onInvalidated() {
        super.onInvalidated();
        if (mFastScroll != null) {
            mFastScroll.onSectionsChanged();
        }
    }
}

最后在onChange()函数会去调用requestLayout() ,从而重新绘制界面。

我们继续看下去

mRecycler.clear();

if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
    mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
    mAdapter = adapter;
}

这里提下这个mRecycler,他和我们的界面重用有关,即他会保存我们的每个Item的View。
然后就是设置mAdapter这事了,如果我们原来就有设置头和脚的话,就会对我们传递过来的adapter做下修改,看来还算挺好的。
接着就是让父类设置下新的adapter,然后就是一堆的设置了,包括对新的额adapter设置数据观察员mDataSetObserver, 告诉mRecycler新的适配器有多少种View类型,好让他做准备,重新设置选中的item的位置。

最后就是请求刷新界面的啦

requestLayout();

我们去看下怎么个布局,关于View的刷新过程,可以去看下源码探索系列11—关于View的绘制,具体不说,我们直接去看下layout函数,下面是AbsListViewOnlayout

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    mInLayout = true;
    final int childCount = getChildCount();
    if (changed) {
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).forceLayout();
        }
        mRecycler.markChildrenDirty();
    }

    layoutChildren();
    mInLayout = false;

    ...
}

这里我们看到,他会去重新绘制界面,同时还去调用了 layoutChildren();,这个方法是空的,之类需要重写他,我们去看下Listview里面写了什么,这个方法不短,也有个三百多行的。我们截取关键的点

protected void layoutChildren() {
    ...
    try {
        ...   
        switch (mLayoutMode) {
            ...
         case LAYOUT_FORCE_BOTTOM:
            sel = fillUp(mItemCount - 1, childrenBottom);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_FORCE_TOP:
            mFirstPosition = 0;
            sel = fillFromTop(childrenTop);
            adjustViewsUpOrDown();
            break;
         ...
         }
      ...
     } finally {
       ...
    }
}

这里有一个有意思的点,就是布局的时候,有两种方式一个是从下到上,另一种是从上到下的布局。平时都没去注意这个布局是从那开始的。我们看下fillUp,从下到上的布局过程的

private View fillUp(int pos, int nextBottom) {
    View selectedView = null;
    int end = 0; 
    ...

    while (nextBottom > end && pos >= 0) {
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
        nextBottom = child.getTop() - mDividerHeight;
        if (selected) {
            selectedView = child;
        }
        pos--;
    }
    mFirstPosition = pos + 1;
    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}

这里重要的一句就是makeAndAddView()函数,他返回的View就是我们的ListView中对应的每个Item。

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
        boolean selected) {
    View child;
    if (!mDataChanged) { 
        child = mRecycler.getActiveView(position);
        if (child != null) {
             setupChild(child, position, y, flow, childrenLeft, selected, true);
            return child;
        }
    }

    child = obtainView(position, mIsScrap);

    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
    return child;
}

这里我们又看到了mRecycler这家伙,前面已经有提到过他和我们的复用有关,这里的getActiviView()函数给了更直接的证据,很显然他保存着我们的View。如果这个Child不是空的话,就去setUpChild(),让我们去给我们绑定数据到View上,但这的前提当然就是我们的数据没变啊,View还可以用的情况下。

如果变了的话,他就调用obtainView()去New一个出来,再让我们去初始化我们的View。
我们先去看下obtainView(),在看那个setupChilde()

View obtainView(int position, boolean[] isScrap) {
     ...
    final View scrapView = mRecycler.getScrapView(position);
    final View child = mAdapter.getView(position, scrapView, this);
    if (scrapView != null) {
        if (child != scrapView) {
            // Failed to re-bind the data, return scrap to the heap.
            mRecycler.addScrapView(scrapView, position);
        } else {
            isScrap[0] = true;
            // Finish the temporary detach started in addScrapView().
            child.dispatchFinishTemporaryDetach();
        }
    }

     ...
    return child;
}

看到这里,我们看到了熟悉的一个地方mAdapter.getView(position, scrapView, this);
这句话就是我们每次写adapter时候遇到的地方

@Override
public View getView(int position, View view, ViewGroup viewGroup) {
    ViewHolder holder;
    if (view == null) {
        view = mInflater.inflate(R.layout.list_item_folder, viewGroup, false);
        holder = new ViewHolder(view);
    } else {
        holder = (ViewHolder) view.getTag();
    }
    ...
}

对于我们的Adapter返回的View,会被保存起来mRecycler.addScrapView(scrapView, position);
这样下次有人问题这个ListView是怎么做到复用的呢?
一定要说用一个mRecycler来保存的,这句话说出来就像看过源码的人。
通过这个mRecycler来保存我们每次infalte后的View,避免了频繁的去生成,从而提高效率的,当然我们都知道,这要求我们滚了一屏才可能,因为过了一屏,新的item的View才是复用原来的。

继续回主线,我们去看下setupChild的内容()

private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
        boolean selected, boolean recycled) {

    ...
    p.viewType = mAdapter.getItemViewType(position);

    if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter
            && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
        attachViewToParent(child, flowDown ? -1 : 0, p);
    } else {
        p.forceAdd = false;
        if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
            p.recycledHeaderFooter = true;
        }
        addViewInLayout(child, flowDown ? -1 : 0, p, true);
    }
    ... 
}

对于我们的child,分了两种,一种是相对特殊的头和脚,一种是正常的item。后者是直接加上去,没什么说,我们看下前者

protected void attachViewToParent(View child, int index, LayoutParams params) {
    child.mLayoutParams = params;

    if (index < 0) {
        index = mChildrenCount;
    }

    addInArray(child, index);

    ...
}

这里主要是把我们的头和脚加入到数组里面去,方便在使用getChildAt(int)的时候可以获得这个View。

到这里基本对Listview的重要的部分就结束啦,他设计到的AdapterView下次再做详细的解析吧

后记

整个的关于源码探索系列的初篇就这样结尾啦!

后面有时间和想法,再写下中篇和大结局篇嘛?

开始写一点设计模式的内容,我们今天的Listview就有用到观察者和适配器模型两种!

热评文章