android列表View,ListView源码分析

ListView结构关系
首先理清listview的层级关系,

271423506402853.jpg

了解一下 AdapterView

public abstract class AdapterView<T extends Adapter> 
           extends ViewGroup {
            省略部分代码
    public void setOnItemClickListener(@Nullable OnItemClickListener listener)  
    public void setOnItemLongClickListener(OnItemLongClickListener listener)
    public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener)
    public abstract T getAdapter();
    public abstract void setAdapter(T adapter);
    public int getCount()
    @Override
    public void addView(View child) {
        throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
    }

//在AdapterView的addView方法中会抛出异常,
//也就是说AdapterView禁用了addView方法。
    @Override
    public void addView(View child, int index) {
        throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
    }
      省略部分代码
    class AdapterDataSetObserver extends DataSetObserver {
        private Parcelable mInstanceState = null;
         //当adapter数据发生改变的时候申请重绘
         //DataSetObserver 用到了观察者模式
        @Override
        public void onChanged() {
        省略部分代码
        }
        @Override
        public void onInvalidated() {
         省略部分代码
    }
}

首先需要说一下RecycleBin的基本原理,这个类也是实现复用的关键类。RecleBin是AbsListView内部类。AbsListView中有一个RecycleBin的对象mRecycler

AbsListView.java

//用于存储不用的view,以便在下次layout中使用来避免创建新的
final RecycleBin mRecycler = new RecycleBin();

RecycleBin使用两级view来进行回收:
ActiveView
:激活view,当前显示在屏幕上的激活view。
ScrapView
:废弃view,被删除的ActiveView会被自动加入ScrapView。

RecycleBin变量说明
AbsListView.java

private RecyclerListener mRecyclerListener;

//存储在mActiveViews中的第一个view的位置
private int mFirstActivePosition;

//布局开始时屏幕显示的view,这个数组会在布局开始时填充,布局结束后所有view被移至mScrapViews。
private View[] mActiveViews = new View[0];
//可以被适配器用作convert view的无序view数组。 这个ArrayList就是adapter中getView方法中的
//参数convertView的来源。注意:这里是一个数组,因为如果adapter中数据有多种类型,
//那么就会有多个ScrapViews。
private ArrayList<View>[] mScrapViews;

//view类型总数,列表中可能有多种数据类型,比如内容数据和分割符
private int mViewTypeCount;

//跟mScrapViews的却别是,mScrapViews是个队列数组,ArrayList<View>[]类型,
//数组长度为mViewTypeCount,而默认ViewTypeCount = 1
//的情况下mCurrentScrap=mScrapViews[0]。
private ArrayList<View> mCurrentScrap;

RecycleBin 方法说明

//为每个子类(子对象)调用forceLayout()。将mScrapView中回收回来的View设置一样标志,
//在下次被复用到ListView中时,告诉viewroot重新layout该view。
//forceLayout()方法只是设置标志,并不会通知其parent来重新layout
public void markChildrenDirty()

//判断给定的view的viewType指明是否可以回收回。
//指定忽略的( ITEM_VIEW_TYPE_IGNORE = -1),
//或者是 HeaderView / (FootViewITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2)是不被回收的。
//如有特殊需要可以将自己定义的viewType设置为-1,否则,将会浪费内存,导致OOM。
 public boolean shouldRecycleViewType(int viewType) {
            return viewType >= 0;
        }

//Clears the scrap heap.清空废弃view堆,并将这些View从窗口中Detach。
void clear()

//获取mActiveViews中指定位置的view,如果找到会将该view从mActiveViews中移除
View getActiveView(int position)

//用AbsListView.的所有子view填充ActiveViews,
//其中childCount是mActiveViews应该保存的最少的view数,
//firstActivePosition是mActiveViews中存储的首个view的位置。
void fillActiveViews(int childCount, int firstActivePosition)

//清掉当前处于transient(转换)状态的所有保存的view
void clearTransientStateViews()

//将view放入scrapview list中
//
void addScrapView(View scrap, int position) 

//
View getScrapView(int position)

//
private View retrieveFromScrap(ArrayList<View> scrapViews, int position)

//
void removeSkippedScrap()
//
private void pruneScrapViews()
//
void reclaimScrapViews(List<View> views)

//
void scrapActiveViews()
//
void setCacheColorHint(int color)

ActivityView就是在UI屏幕上可见的视图(onScreenView),也是与用户进行交互的View。这些View会通过RecycleBin直接存储到mActivityView数组当中。
当滑动ListView的时候,有些View被滑动到屏幕之外(offScreen) View,这些View就成为了ScrapView,就是废弃的View,已经无法与用户进行交互了,此时在UI视图改变的时候就没有绘制视图的必要。这些Veiw被RecycleBin存在mScrapView数组当中,但是没有被销毁掉,目的是为了二次复用,也就是间接复用。

选区_131.png

当新的View需要显示的时候,先判断mActiveViews中是否存在,如果存在那么就可以从mActivityView数组当中直接取出复用,直接复用,否则从mScrapView数组当中进行判断,存在,二次复用当前的视图,不存在,就需要inflate View了。

选区_132.png

ListView比如第一次绘制 会执行 onMeasure->onLayout->onDraw
比如数据改变,申请requestLayout

调用的函数的堆栈图如下。(这里只是选择的特殊的一条函数执行路径)

选区_133.png

AbsListView.java

    /**
     * 子类不必覆写这个方法,覆写layoutChildren代替
     *  
     */
    @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();
        }
        //对item进行布局的流程
        layoutChildren();
        mInLayout = false;

        省略部分代码
    }

fillFromTop 如下

ListView.java


    private View fillDown(int pos, int nextTop) {
        View selectedView = null;
    /**
     * end用来判断Item是否已经将ListView填充满
     */
        int end = (mBottom - mTop);
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            end -= mListPadding.bottom;
        }

         /**
          * nextTop < end确保了我们只要将新增的子View能够覆盖ListView的界面就可以了
          *pos < mItemCount确保了我们新增的子View在Adapter中都有对应的数据源item
          */
        while (nextTop < end && pos < mItemCount) {
            // is this the selected item?
            boolean selected = pos == mSelectedPosition;
            View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

            nextTop = child.getBottom() + mDividerHeight;
            if (selected) {
                selectedView = child;
            }
            pos++;
        }

        setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
        return selectedView;
    }

上面函数的理解

选区_134.png

makeAndAddView如下

ListView.java

    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {

       
        if (!mDataChanged) {
            // Try to use an existing view for this position.
            final View activeView = mRecycler.getActiveView(position);
            if (activeView != null) {
                 //如果存在activeView,使用activeView
                // Found it. We're reusing an existing child, so it just needs
                // to be positioned like a scrap view.
                setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                return activeView;
            }
        }

       
        // Make a new view for this position, or convert an unused view if
        // possible.
        final View child = obtainView(position, mIsScrap);

        // This needs to be positioned and measured.
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

obtainView的如下

AbListView.java

    View obtainView(int position, boolean[] outMetadata) {
        省略部分代码
        final View scrapView = mRecycler.getScrapView(position);
        // mAdapter.getView登场
        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 if (child.isTemporarilyDetached()) {
                outMetadata[0] = true;

                // Finish the temporary detach started in addScrapView().
                child.dispatchFinishTemporaryDetach();
            }
        }
        省略部分代码
        return child;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容