Android ListView初始化简单分析

下面是分析ListView初始化的源码流程分析,主要是ListVIew.onLayout过程与普通视图的layout过程完全不同,避免流程交代不清楚,以下是一个流程的思维导图。

思维导图是顺序是从左向右,从上向下。

一、 先看构造函数,上图中1.1就不分析了,主要是读取一些ListView参数,直接来看1.2 ViewGroup构造函数源码

private void initViewGroup() {
    ......
    // 初始化保存当前ViewGroup中所有View的数组
    mChildren = new View[ARRAY_INITIAL_CAPACITY];
    // 初始时其Child个数为0
    mChildrenCount = 0;
    ......
}  

视图的创建过程的都会执行的三个步骤: onMeasure, onLayout, onDraw

二、接着2 即 ListView.onMeasure方法,只是获取当前ListView的宽高

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // Sets up mListPadding
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);  

    // 获取当前ListView总宽高
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);  

    ......  

    setMeasuredDimension(widthSize , heightSize);
    mWidthMeasureSpec = widthMeasureSpec;
}  

三、步骤3是重点,AbsListView.onLayout的流程与普通View的不同

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

    if (mFastScroller != null && mItemCount != mOldItemCount) {
        mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
    }  

    // ListView实现此方法
    layoutChildren();
    mInLayout = false;  

    mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
} 

四、步骤4.1 具体分析ListVIew.layoutChildren

@Override
protected void layoutChildren() {
    // 默认为false
    final boolean blockLayoutRequests = mBlockLayoutRequests;
    if (!blockLayoutRequests) {
        // 目的是为了阻止没必要的layout操作,提交效率
        mBlockLayoutRequests = true;
    } else {
        return;
    }  

    try {
        super.layoutChildren();
        // 为什么此处要请求重绘?
        invalidate();  

        ......  

        int childCount = getChildCount();  

        ......  

        boolean dataChanged = mDataChanged;
        if (dataChanged) {
            handleDataChanged();
        }  

        ......  

        // 把所有child view放置到RecycleBin
        // 满足条件的话可以重用这些child view
        final int firstPosition = mFirstPosition;  

        // ListView中Item复用使用此数据结构
        final RecycleBin recycleBin = mRecycler;  

        // reset the focus restoration
        View focusLayoutRestoreDirectChild = null;  

        // Don‘t put header or footer views into the Recycler. Those are
        // already cached in mHeaderViews;
        if (dataChanged) {
            for (int i = 0; i < childCount; i++) {
                recycleBin.addScrapView(getChildAt(i), firstPosition+i);
            }
        } else {
            // 创建childCount个数的View放入RecycleBin类activeViews数组中
            recycleBin.fillActiveViews(childCount, firstPosition);
        }  

        ......  

        // Clear out old views
        detachAllViewsFromParent();
        recycleBin.removeSkippedScrap();  

        switch (mLayoutMode) {  

        ......  

        default:
            if (childCount == 0) {
                if (!mStackFromBottom) {
                    final int position = lookForSelectablePosition(0, true);
                    setSelectedPositionInt(position);
                    // 此方法是重点,以下具体分析
                    sel = fillFromTop(childrenTop);
                } else {
                    final int position = lookForSelectablePosition(mItemCount - 1, false);
                    setSelectedPositionInt(position);
                    sel = fillUp(mItemCount - 1, childrenBottom);
                }
            } else {
                if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                    sel = fillSpecific(mSelectedPosition,
                            oldSel == null ? childrenTop : oldSel.getTop());
                } else if (mFirstPosition < mItemCount) {
                    sel = fillSpecific(mFirstPosition,
                            oldFirst == null ? childrenTop : oldFirst.getTop());
                } else {
                    sel = fillSpecific(0, childrenTop);
                }
            }
            break;
        }  

        // Flush any cached views that did not get reused above
        recycleBin.scrapActiveViews();  

         ......  

        invokeOnItemScrollListener();
    } finally {
        if (!blockLayoutRequests) {
            mBlockLayoutRequests = false;
        }
    }

五、 分析步骤4.2 ListView.fillFromTop源码

// 从上向下在ListView中填充Item View
private View fillFromTop(int nextTop) {
       mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
       mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
       if (mFirstPosition < 0) {
           mFirstPosition = 0;
       }
       // 具体操作在此
       return fillDown(mFirstPosition, nextTop);
   } 

六、查看步骤4.3 ListView.fillDown源码

// 在参数pos下面填充Item View
private View fillDown(int pos, int nextTop) {
    View selectedView = null;  

    // ListView getHeight也是这样计算的
    int end = (mBottom - mTop);
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
        end -= mListPadding.bottom;
    }  

    // 初始化时pos = 0,如果item总数少于一屏幕,执行mItemCount - pos次
    // 如果item多余一屏幕,执行end - nextTop次
    while (nextTop < end && pos < mItemCount) {
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        // 获取Item View对象
        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;
}

七、查看步骤4.4 ListView.makeAndAddView源码

// ListView都是通过此方法获取Item View
// 具体Item View如何复用,是否需要创建新的Item View都有此方法处理
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
        boolean selected) {
    View child;  

 ListView的数据发生变化,肯定Item View之前已经创建好了,无需重新创建
    if (!mDataChanged) {
        // 当前position复用之前创建的视图
        child = mRecycler.getActiveView(position);
        if (child != null) {
            // 对复用的View针对当前需要进行配置
            setupChild(child, position, y, flow, childrenLeft, selected, true);  

            return child;
        }
    }  

    // 创建或者重用视图
    child = obtainView(position, mIsScrap);  

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

    return child;
}  

八 查看步骤4.5 ListView.setupChild源码

private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
        boolean selected, boolean recycled) {
    // 判断当前Item View是否选中状态
    final boolean isSelected = selected && shouldShowSelector();
    final boolean updateChildSelected = isSelected != child.isSelected();  

    final int mode = mTouchMode;  

    // 是否处于按下状态
    final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
            mMotionPosition == position;
    final boolean updateChildPressed = isPressed != child.isPressed();  

    // 是否需要重新measure与layout
    final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();  

    // Respect layout params that are already in the view. Otherwise make some up...
    // noinspection unchecked
    AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
    if (p == null) {
        p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
    }
    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;
        }
        // 向ListView(ViewGroup子类)添加当前Item View
        addViewInLayout(child, flowDown ? -1 : 0, p, true);
    }  

    // 更新选中状态
    if (updateChildSelected) {
        child.setSelected(isSelected);
    }  

    // 更新按下状态
    if (updateChildPressed) {
        child.setPressed(isPressed);
    }  

    if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
        if (child instanceof Checkable) {
            ((Checkable) child).setChecked(mCheckStates.get(position));
        } else if (getContext().getApplicationInfo().targetSdkVersion
                >= android.os.Build.VERSION_CODES.HONEYCOMB) {
            child.setActivated(mCheckStates.get(position));
        }
    }  

    if (needToMeasure) {
        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
                mListPadding.left + mListPadding.right, p.width);
        int lpHeight = p.height;
        int childHeightSpec;
        if (lpHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }
        // 与普通视图的measure流程不同,ListView是在此处执行具体的当前Item View measure
        child.measure(childWidthSpec, childHeightSpec);
    } else {
        cleanupLayoutState(child);
    }  

    final int w = child.getMeasuredWidth();
    final int h = child.getMeasuredHeight();
    final int childTop = flowDown ? y : y - h;  

    if (needToMeasure) {
        final int childRight = childrenLeft + w;
        final int childBottom = childTop + h;
        // 大小改变肯定位置也会发生变化,当前Item View重新进行layout
        child.layout(childrenLeft, childTop, childRight, childBottom);
    } else {
        child.offsetLeftAndRight(childrenLeft - child.getLeft());
        child.offsetTopAndBottom(childTop - child.getTop());
    }  

    if (mCachingStarted && !child.isDrawingCacheEnabled()) {
        child.setDrawingCacheEnabled(true);
    }  

    if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
            != position) {
        child.jumpDrawablesToCurrentState();
    }
} 
时间: 2024-10-12 19:28:25

Android ListView初始化简单分析的相关文章

Android.mk文件简单分析

Android.mk文件简单分析 一个Android.mk文件用来向编译系统描述需要编译的源代码.具体来说:该文件是GNUMakefile的一小部分,会被编译系统解析一次或多次.可以在每一个Android.mk中定义一个或多个模块,也可以在几个模块中使用同一个源代码文件. 每个模块属下列类型之一: 1)APK程序,一般的Android程序,编译打包生成apk文件 2)JAVA库,java类库,编译打包生成jar文件 3)  C\C++应用程序,可执行的C\C++应用程序 4)C\C++静态库,编

Android ListView初始化将实例化多少个item

下面是分析ListView初始化的源码流程分析. 在AbsListView.onLayout中会调用layoutChildren(),由listview实现 @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 = getChildCoun

Android——ListView实现简单列表

最近做一个black ant的温控系统项目,里面有很多列表项,但是用的时候,感觉封装的已经挺好的了,自己拿过来改改代码就行了,所以用过之后也没什么感觉.现在趁着闲暇时间整理下简单的ListView,体会下这个东西到底是怎么个原理. 首先看下实现效果: 其中,每一条列表项加的是一个Image跟一个TextView,数据源绑定在了TextView上面. 首先,添加两个layout文件: 列表(item)的布局文件: <?xml version="1.0" encoding="

Android Launcher 3 简单分析

最近在学习Android Launcher的相关知识,在github上找到可以在Android studio上编译的Launcher 3代码,地址:https://github.com/rydanliu/Launcher3 Launcher 3的界面主要由SearchDropTargetBar.Workspace.CellLayout.PageIndicator.Hotseat组成.如下图: Launcher 3 最主要的是一个Activity,基本上所有操作都集中在这个Activity上.这个

Android ListView滑动过程中控件显示重复/错误问题之原理分析及解决方案

前言: 为了使ListView性能更优,最普遍的方法就是添加一个ViewHolder静态类. 虽然性能有很大的提高,但是同样也伴随着Item控件内容显示重复或错乱的情况. 分析并解决如下两个问题 一.控件数据未初始化而导致的显示错误. 二.网络异步加载导致出现显示错误.重复. 如下我们来简单分析一下ListView的缓存机制.我们整篇文章均以下图的模型来举例说明. (图片转至http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.h

Android开源项目pulltorefresh分析与简单使用

在Android开发中有时我们需要访问网络实时刷新数据,比如QQ好友在线状态最新信息,QQ空间需要显示更多的好友动态信息,EOE论坛客户端显示更多的文章帖子信息等.android-pulltorefresh开源项目提供一个向下滑动即刷新列表的功能,将该项目稍作修改即可应用到自己的项目中. 1.下载地址 https://github.com/johannilsson/android-pulltorefresh 该项目为 Android 应用提供一个向下滑动即刷新列表的功能. 2.工程组成 Pull

Android ListView异步加载图片乱序问题,原因分析及解决方案

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/45586553 在Android所有系统自带的控件当中,ListView这个控件算是用法比较复杂的了,关键是用法复杂也就算了,它还经常会出现一些稀奇古怪的问题,让人非常头疼.比如说在ListView中加载图片,如果是同步加载图片倒还好,但是一旦使用异步加载图片那么问题就来了,这个问题我相信很多Android开发者都曾经遇到过,就是异步加载图片会出现错位乱序的情况.遇到这个问题时,不

android:clipToPadding属性的分析——以ListView的&quot;别样&quot;padding为例

MainActivity如下: package cn.com.bravesoft.testlistviewloadmore; import java.util.ArrayList; import java.util.HashMap; import android.app.Activity; import android.os.Bundle; import android.widget.ListView; import android.widget.SimpleAdapter; /** * Dem

android 从源码分析为什么Listview初次显示时没滚动却自动调用onScroll方法的原因

我们做Listview的分批加载时,需要为Listview调用setOnScrollListener(具体代码可见我上一篇博客) 可是,我们会发现,当运行程序时,listview明明没有滚动,那为什么系统会调用onScroll方法呢?(补充:此时onScrollStateChanged并不会调用) 我们先看setOnScrollListener源码: public void setOnScrollListener(OnScrollListener l) { mOnScrollListener =