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

来源:互联网 发布:信号屏蔽软件 编辑:程序博客网 时间:2024/06/06 09:57

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

起航

API:23

我们的Listview是继承于AbsListView

public class ListView extends AbsListView 

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

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

@Overridepublic 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

@Overrideprotected 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时候遇到的地方

@Overridepublic 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的重要的部分就结束啦!

后记

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

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

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

0 0
原创粉丝点击