首页 > 学院 > 开发设计 > 正文

ListView源码分析(一)

2019-11-07 23:04:54
字体:
来源:转载
供稿:网友

  这几天把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); } }

这里注意两个对象:

View[] mActiveViews:存放的是当前ListView可以使用的待激活的子item viewArrayList[] 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(); }
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表