ListView源码分析(一)

来源:互联网 发布:淘宝助理免费吗 编辑:程序博客网 时间:2024/06/15 07:26

  这几天把ListView源码看了下,基本整理下思路并写了这篇博客,也是对学习源码的一个记录。

首先看ListView的构造方法干了些什么

public ListView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);//父类AbsListview中初始化一些属性,比如焦点setFocusableInTouchMode(true),绘制setWillNotDraw(false)等        TypedArray a = context.obtainStyledAttributes(attrs,                com.android.internal.R.styleable.ListView, defStyle, 0);        CharSequence[] entries = a.getTextArray(                com.android.internal.R.styleable.ListView_entries);        if (entries != null) {            setAdapter(new ArrayAdapter<CharSequence>(context,                    com.android.internal.R.layout.simple_list_item_1, entries));        }        //获取item分割线 drawable 可以自定义        final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider);        if (d != null) {            // If a divider is specified use its intrinsic height for divider height            setDivider(d);        }        //头部样式        final Drawable osHeader = a.getDrawable(                com.android.internal.R.styleable.ListView_overScrollHeader);        if (osHeader != null) {            setOverscrollHeader(osHeader);        }        //尾部样式        final Drawable osFooter = a.getDrawable(                com.android.internal.R.styleable.ListView_overScrollFooter);        if (osFooter != null) {            setOverscrollFooter(osFooter);        }        // Use the height specified, zero being the default        final int dividerHeight = a.getDimensionPixelSize(                com.android.internal.R.styleable.ListView_dividerHeight, 0);        if (dividerHeight != 0) {            setDividerHeight(dividerHeight);        }        mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);        mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);        a.recycle();    }

上面构造方法做了一些初始化工作,并设置了默认样式

onMeasure()方法

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        // Sets up mListPadding        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //格式化并取出MeasureSpec的模式和测量值大小        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int childWidth = 0;        int childHeight = 0;        int childState = 0;        mItemCount = mAdapter == null ? 0 : mAdapter.getCount();        if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||heightMode == MeasureSpec.UNSPECIFIED)) {            final View child = obtainView(0, mIsScrap);            //测量childview            measureScrapChild(child, 0, widthMeasureSpec);            childWidth = child.getMeasuredWidth();            childHeight = child.getMeasuredHeight();            childState = combineMeasuredStates(childState, child.getMeasuredState());            if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(                    ((LayoutParams) child.getLayoutParams()).viewType)) {                mRecycler.addScrapView(child, -1);            }        }        if (widthMode == MeasureSpec.UNSPECIFIED) {            widthSize = mListPadding.left + mListPadding.right + childWidth +                    getVerticalScrollbarWidth();        } else {            widthSize |= (childState&MEASURED_STATE_MASK);        }        if (heightMode == MeasureSpec.UNSPECIFIED) {            heightSize = mListPadding.top + mListPadding.bottom + childHeight +                    getVerticalFadingEdgeLength() * 2;        }        if (heightMode == MeasureSpec.AT_MOST) {            // TODO: after first layout we should maybe start at the first visible position, not 0            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);        }        setMeasuredDimension(widthSize , heightSize);        mWidthMeasureSpec = widthMeasureSpec;            }private void measureScrapChild(View child, int position, int widthMeasureSpec) {        LayoutParams p = (LayoutParams) child.getLayoutParams();        if (p == null) {            p = (AbsListView.LayoutParams) generateDefaultLayoutParams();            child.setLayoutParams(p);        }        p.viewType = mAdapter.getItemViewType(position);        p.forceAdd = true;        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,                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);        }        child.measure(childWidthSpec, childHeightSpec);    }

上述操作对ListView的测量并赋值给成员变量 ,注意ListView第一次创建的时候并没有mAdapter的存在,只有在setAdapter被我们调用过后才会执行这些方法,也就是说在setAdapter中一定会调用requestLayout方法重新走一遍流程。

onLayout方法

  ListView中并没有onLayout方法,那也就是说一定是在他的父类AbsListView中,我们可以看到它调用了layoutChildren(),从方法名看应该是对子view进行布局,这个layoutChildren是一个空实现方法,也就是说应该是通过AbsListView的子类ListVIew和GridView进行实现

protected void onLayout(boolean changed, int l, int t, int r, int b) {        super.onLayout(changed, l, t, r, b);        mInLayout = true;        //拿到view 数量        final int childCount = getChildCount();        if (changed) {            for (int i = 0; i < childCount; i++) {                getChildAt(i).forceLayout();            }            mRecycler.markChildrenDirty();        }        // 由子类ListView 和 GridView实现,是核心布局方法代码,也是listview与adapter交互数据的主要入口函数        layoutChildren();        mInLayout = false;        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;        // TODO: Move somewhere sane. This doesn't belong in onLayout().        if (mFastScroll != null) {            mFastScroll.onItemCountChanged(getChildCount(), mItemCount);        }    }

这里注意两个对象:

  1. View[] mActiveViews:存放的是当前ListView可以使用的待激活的子item view
  2. ArrayList[] mScrapViews:存放的是在ListView滑动过程中滑出屏幕来回收以便下次利用的子item view

这里写图片描述

@Override    protected void layoutChildren() {        ...        final int firstPosition = mFirstPosition;        final RecycleBin recycleBin = mRecycler;        // 只有在调用adapter.notifyDatasetChanged()方法一直到layout()布局结束,        //dataChanged为true,默认为false,这里如果调用notifyDatasetChanged,就会将Item添加到ReyclerBin当中,这个        //ReyclerBin封装了这两个集合用来存放对应的符合条件的item,用来实现复用机制        //1.View[] mActiveViews : 存放的是当前ListView可以使用的待激活的子item view        //2.ArrayList<View>[] mScrapViews : 存放的是在ListView滑动过程中滑出屏幕来回收以便下次利用的子item view        if (dataChanged) {            // dataChanged为true,说明当前listview是有数据的了,把当前所有的item view            // 存放到RecycleBin对象的mScrapViews中保存            for (int i = 0; i < childCount; i++) {                recycleBin.addScrapView(getChildAt(i), firstPosition+i);            }        } else {                // dataChanged默认为false,第一次执行此方法走这里                //将view添加到 activeViews[] 中            recycleBin.fillActiveViews(childCount, firstPosition);        }           ...        switch (mLayoutMode) {            ...            default:                //一般情况下走这里                if (childCount == 0) {                    // 第一次布局的时候,因为还没有setAdapter,没有走mAdpate.getCount方法,所以childCount必然为0                    if (!mStackFromBottom) {                        final int position = lookForSelectablePosition(0, true);                        setSelectedPositionInt(position);                        // 从上到上布局listview能显示得下的子view,具体的填充view的方法,下面讲到                        sel = fillFromTop(childrenTop);                    } else {                        final int position = lookForSelectablePosition(mItemCount - 1, false);                        setSelectedPositionInt(position);                        sel = fillUp(mItemCount - 1, childrenBottom);                    }                } else {                    // 非第一次layout,也就说执行了nitifyDatasetChanged方法之后                    if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {                        sel = fillSpecific(mSelectedPosition,                                oldSel == null ? childrenTop : oldSel.getTop());                    } else if (mFirstPosition < mItemCount) {                    // 通常情况走这里,fillSpecific()会调用fillUp()和fillDown()布局子view                        sel = fillSpecific(mFirstPosition,                                oldFirst == null ? childrenTop : oldFirst.getTop());                    } else {                        sel = fillSpecific(0, childrenTop);                    }                }                break;            }        //到这里,ListView中的view就被填充完毕.        ...        //布局完成之后记录状态        mLayoutMode = LAYOUT_NORMAL;        mDataChanged = false;    }

Item布局的填充
在layoutChidren中有几个以fill开头的方法就是具体的Item的填充方法。fillSpecific()会根据mStackFromBottom参数判断填充方向,通过fillUp,fillDown进行填充

    private View fillSpecific(int position, int top) {        boolean tempIsSelected = position == mSelectedPosition;        View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);        // Possibly changed again in fillUp if we add rows above this one.        mFirstPosition = position;        View above;        View below;        final int dividerHeight = mDividerHeight;        //根据填充方向,如果mStackFromBottom为false,表示从顶部向底部填充,true反之        //mStackFromBottom 可以通过 xml文件android:stackFromBottom="false"设置,默认为false        if (!mStackFromBottom) {            //具体填充方法            above = fillUp(position - 1, temp.getTop() - dividerHeight);            // This will correct for the top of the first view not touching the top of the list            adjustViewsUpOrDown();            //具体填充方法            below = fillDown(position + 1, temp.getBottom() + dividerHeight);            int childCount = getChildCount();            if (childCount > 0) {                correctTooHigh(childCount);            }        } else {            below = fillDown(position + 1, temp.getBottom() + dividerHeight);            // This will correct for the bottom of the last view not touching the bottom of the list            adjustViewsUpOrDown();            above = fillUp(position - 1, temp.getTop() - dividerHeight);            int childCount = getChildCount();            if (childCount > 0) {                 correctTooLow(childCount);            }        }        if (tempIsSelected) {            return temp;        } else if (above != null) {            return above;        } else {            return below;        }    }

具体填充布局可以看我的另一篇博客ListView源码分析(二)

setAdapter

setAdapter中通过mAdapter.registerDataSetObserver(mDataSetObserver)注册一个AdapterDataSetObserver订阅者,每当调用notifyDataSetChange的时候,就会触发AdapterDataSetObserver的onChanged的方法,这个是观察者模式,这个方法最终调用requestLayout方法,也就是说我们每次setAdapter之后就会重新布局,这时候mAdapter不为空。

    @Override    public void setAdapter(ListAdapter adapter) {        if (mAdapter != null && mDataSetObserver != null) {            mAdapter.unregisterDataSetObserver(mDataSetObserver);        //将一些成员变量还原设置为初始默认值         //mLayoutMode = LAYOUT_NORMAL        resetList();        // mRecycler的mScrapViews清空并执行listview.removeDetachedView        //mScrapViews 存放边界之外的view        mRecycler.clear();        if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {             // 如果listview有headerView或者FooterView则会生成包装adapter,生成一个含有HeaderView 和footerView的adapter            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);        } else {            mAdapter = adapter;        }        mOldSelectedPosition = INVALID_POSITION;//-1        mOldSelectedRowId = INVALID_ROW_ID;//Long.MIN_VALUE        // AbsListView#setAdapter will update choice mode states.        //给父亲 adblistView 设置 adapter        super.setAdapter(adapter);        if (mAdapter != null) {            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();            mOldItemCount = mItemCount;            //调用adapter的getCount 得到条目个数            mItemCount = mAdapter.getCount();            checkFocus();            //注册观察者,这个观察者每当调用notifyDataSetChange的时候就会触发            mDataSetObserver = new AdapterDataSetObserver();            mAdapter.registerDataSetObserver(mDataSetObserver);             // 设置listview的数据源类型,并在mRecycler中初始化对应个数的scrapViews list            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();        }        // 会调用顶层viewRootImpl.performTraversals(),导致视图重绘,listview刷新        requestLayout();    }

notifyDataSetChanged

这个方法在BaseAdapter中

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

这时候根据观察者模式,会调用订阅者AdapterDataSetObserver的onChanged方法,上面提到过,最终还是会调用requestLayout进行重新布局

    @Override    public void onChanged() {        mDataChanged = true;        mOldItemCount = mItemCount;        mItemCount = getAdapter().getCount();        // Detect the case where a cursor that was previously invalidated has        // been repopulated with new data.        if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null                && mOldItemCount == 0 && mItemCount > 0) {            AdapterView.this.onRestoreInstanceState(mInstanceState);            mInstanceState = null;        } else {            rememberSyncState();        }        checkFocus();        // 同样,最终调用viewRootImpl.performTraversals(),导致视图重绘,执行listview的         // measure layout 方法等        requestLayout();    }
0 0
原创粉丝点击