一、概述

这部分主要是讲解ListView在初始时,即setAdapter之后,如何将Adapter中取得的view添加到ListView中,主要涉及到几个方法:

layoutChildren,fillFromTop,fillDown / fillUp,makeAndAddView,obtainView,setupChild;

二、讲解

layoutChildren是个最重要的方法,也很复杂,本篇略过,会在以后中提到,不过,为了讲解其它方法,还是提一下:

    @Override
protected void layoutChildren() {
......... boolean dataChanged = mDataChanged;
if (dataChanged) {
handleDataChanged();
} ......... // Clear out old views
detachAllViewsFromParent(); switch (mLayoutMode) {
......... case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break; .........
} .........
}

setAdapter之后,dataChanged是true,然后走到handleDataChanged方法,在这个方法中会设置mLayoutMode,通常是LAYOUT_FORCE_TOP,即从顶部开始一个一个的往下添加childview:

    @Override
protected void handleDataChanged() {
......... // Nothing is selected. Give up and reset everything.
mLayoutMode = mStackFromBottom ? LAYOUT_FORCE_BOTTOM : LAYOUT_FORCE_TOP;
mSelectedPosition = INVALID_POSITION;
mSelectedRowId = INVALID_ROW_ID;
mNextSelectedPosition = INVALID_POSITION;
mNextSelectedRowId = INVALID_ROW_ID;
mNeedSync = false;
mSelectorPosition = INVALID_POSITION;
checkSelectionChanged();
}

这个方法的最后,就会去设置mLayoutMode,并设置其它position为INVALID属性;
        回到layoutChildren中,设置mFirstPosition=0后,将childrenTop(=0或padding top 后的值),进入fillFromTop方法:

    /**
* Fills the list from top to bottom, starting with mFirstPosition
*
* @param nextTop The location where the top of the first item should be
* drawn
*
* @return The view that is currently selected
*/
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);
}

该方法,第一个child的position起始点,然后调用fillDown;

    /**
* Fills the list from pos down to the end of the list view.
*
* @param pos The first position to put in the list
*
* @param nextTop The location where the top of the item associated with pos
* should be drawn
*
* @return The view that is currently selected, if it happens to be in the
* range that we draw.
*/
private View fillDown(int pos, int nextTop) {
View selectedView = null; int end = (mBottom - mTop);
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
} 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++;
} return selectedView;
}

首先,获得可视区域的高度(mBottom - mTop);

其次,while循环中,判断:累计添加到listview中child的高度,不超过可视区域(添加最后一个child时,有可能只显示部分),且添加的child的下标不超过总的个数(否则系统会报 OutOfBounds 的异常);

在循环中,会去调用makeAndAddView,这个方法不会真正的去添加child,但会调用之后的setupChild来真正添加到listview中:

    /**
* Obtain the view and add it to our list of children. The view can be made
* fresh, converted from an unused view, or used as is if it was in the
* recycle bin.
*
* @param position Logical position in the list
* @param y Top or bottom edge of the view to add
* @param flow If flow is true, align top edge to y. If false, align bottom
* edge to y.
* @param childrenLeft Left edge where children should be positioned
* @param selected Is this position selected?
* @return View that was added
*/
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child; if (!mDataChanged) {
// Try to use an existing view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
position, getChildCount());
} // Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true); return child;
}
} // Make a new view for this position, or convert an unused view if possible
child = obtainView(position, mIsScrap); // This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child;
}

该方法分两种情况:
        1. adapter中的数据发生了变化,初始setAdapter,或之后我们将adapter中的数据做了新增/删除后,调用Adapter.notifyDataSetChanged;变化了,就会从RecycleBin中的mScrapView中,取之前移出的view,即convertView来复用;

2. 若没有变化,则从RecycleBin的mActiveView中取当前显示的view(为啥会有这种情况?当listview稳定后,我们不滚动它,但有可能点击或长按),这时就会走到这里。

obtainView就是从RecycleBin中,取移出去的View,传给Adapter.getView方法(convertView),我们先来看看这个方法:

    /**
* Get a view and have it show the data associated with the specified
* position. This is called when we have already discovered that the view is
* not available for reuse in the recycle bin. The only choices left are
* converting an old view or making a new one.
*
* @param position The position to display
* @param isScrap Array of at least 1 boolean, the first entry will become true if
* the returned view was taken from the scrap heap, false if otherwise.
*
* @return A view displaying the data associated with the specified position
*/
View obtainView(int position, boolean[] isScrap) {
isScrap[0] = false;
View scrapView; scrapView = mRecycler.getScrapView(position); View child;
if (scrapView != null) {
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,
position, -1);
} child = mAdapter.getView(position, scrapView, this); if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
position, getChildCount());
} if (child != scrapView) {
mRecycler.addScrapView(scrapView, position);
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
position, -1);
}
} else {
isScrap[0] = true;
child.dispatchFinishTemporaryDetach();
}
} else {
child = mAdapter.getView(position, null, this);
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
position, getChildCount());
}
} return child;
}

这个方法,实际上是在AbsListView类中的,我们知道,ListView, GridView是继承于AbsListView的,而AbsListView是继承于AdapterView<ListAdapter>,而这个是继承于ViewGroup,所以,ListView或AbsListView中,有些变量或方法,都是继承过来的,我们看源码时,需要结合这些类一起看(比较头痛,跳来跳去),但熟悉之后,对我们写出高质量的程序是很有帮助的。

小提示:RecycleBin类,它的作用就是一个View的缓存,将移出屏幕外的view回收,并给新移入到屏幕内的view来复用,这样就能节省大量内存(之后讲解该类)。

回归正题obtainView方法:

1. 先从RecycleBin中获取一个ScrapView,如果缓存中没有,则convertView为null,在Adapter中,需要自己去LayoutInflater一个view;

2. 若有,将这个convertView传给Adapter.getView方法;

3. 判断从getView方法中返回的view是否与scrapview一致,如果一致,表明是复用的,反之,则程序又去创建了一个新的view(浪费了一块内存),且将得到的scrapview重新加入到RecycleBin.mScrapView中;

回到makeAndAddView方法,将child传给setupChild,开始真正的加入到listView中去显示:

    /**
* Add a view as a child and make sure it is measured (if necessary) and
* positioned properly.
*
* @param child The view to add
* @param position The position of this child
* @param y The y position relative to which this view will be positioned
* @param flowDown If true, align top edge to y. If false, align bottom
* edge to y.
* @param childrenLeft Left edge where children should be positioned
* @param selected Is this position selected?
* @param recycled Has this view been pulled from the recycle bin? If so it
* does not need to be remeasured.
*/
private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
boolean selected, boolean recycled) {
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();
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 = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}
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);
} 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);
}
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;
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();
}
}

attachViewToParent 和 addViewInLayout两者大致差不多,都是将view添加到parent view的array中,区别在于,attachView是不用去requestLayout的,而addViewInLayout的最后一个参数指明(true不用requestLayout,false则需要requestLayout),这两个方法都在ViewGroup中。

flowDown ? -1 : 0 , 不知道大家注意没有, -1 和 0 的区别?我们看下attachViewToParent代码吧:

    /**
* Attaches a view to this view group. Attaching a view assigns this group as the parent,
* sets the layout parameters and puts the view in the list of children so it can be retrieved
* by calling {@link #getChildAt(int)}.
*
* This method should be called only for view which were detached from their parent.
*
* @param child the child to attach
* @param index the index at which the child should be attached
* @param params the layout parameters of the child
*
* @see #removeDetachedView(View, boolean)
* @see #detachAllViewsFromParent()
* @see #detachViewFromParent(View)
* @see #detachViewFromParent(int)
*/
protected void attachViewToParent(View child, int index, LayoutParams params) {
child.mLayoutParams = params; if (index < 0) {
index = mChildrenCount;
} addInArray(child, index); child.mParent = this;
child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK & ~DRAWING_CACHE_VALID) |
DRAWN | INVALIDATED;
this.mPrivateFlags |= INVALIDATED; if (child.hasFocus()) {
requestChildFocus(child, child.findFocus());
}
}

如果是-1,则将index = mChildrenCount,mChildrenCount就是当前parent view已经有多少个child view,这里将index设置为当前child view个数,意在表明如果是 -1 则将该view加入到parent view中的child view最后,如果是0, 则加入到child view 数组中的第一个;即:该view是插入到顶部,还是添加到底部。
        默认情况下,添加到listview中的item,即child都会measure一次高度和宽度,然后,调用child.layout,通知新添加的child,layout一下它里面的children。

然后,然后就没有了然后,整个流程走完,回到ListView.layoutChildren中,adjustViewsUpOrDown将所有child调整对齐,刷新一下RecycleBin的Active和Scrap缓存,调用updateScrollIndicators更新一下滚动条的值,若有注意OnScrollListener,也通知一下invokeOnItemScrollListener。

三、总结

虽然,只讲了一个fillDown,其实fillUp以及ListView中的其它fillXXX方法,是差不多的,本篇的目的,也只是希望大家了解ListView / GridView继承于AbsListView时,如何将child添加进去。

之后,会涉及ListView / GridView, AbsListView 以及其内部类 RecycleBin的一些讲解,也欢迎大家留言,一起讨论。

Android ListView的理解(一)的更多相关文章

  1. Android ListView工作原理完全解析,带你从源码的角度彻底理解

    版权声明:本文出自郭霖的博客,转载必须注明出处.   目录(?)[+] Adapter的作用 RecycleBin机制 第一次Layout 第二次Layout 滑动加载更多数据   转载请注明出处:h ...

  2. Android ListView工作原理全然解析,带你从源代码的角度彻底理解

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/44996879 在Android全部经常使用的原生控件其中.使用方法最复杂的应该就是 ...

  3. [译]:Xamarin.Android开发入门——Hello,Android Multiscreen深入理解

    原文链接:Hello, Android Multiscreen_DeepDive. 译文链接:Xamarin.Android开发入门--Hello,Android Multiscreen深入理解. 本 ...

  4. Android动画的理解

    基础知识 在我们开始讲Android动画这个知识点之前,我们了解下相应的基础知识点. Shape篇 一般用Shape定义的XML文件是存放在Drawable目录下,广泛应用于在Button.TextV ...

  5. 【腾讯Bugly干货分享】Android ListView与RecyclerView对比浅析--缓存机制

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/5811d3e3ab10c62013697408 作者:黄宁源 一,背景 Recy ...

  6. Android ListView无法触发ItemClick事件

    Android ListView无法触发ItemClick事件 开发中很常见的一个问题,项目中的listview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承Base ...

  7. 实现Android ListView 自动加载更多内容

    研究了几个小时终于实现了Android ListView 自动加载的效果. 说说我是怎样实现的.分享给大家. 1.给ListView增加一个FooterView,调用addFooterView(foo ...

  8. [置顶] android ListView包含Checkbox滑动时状态改变

    题外话: 在xamarin android的开发中基本上所有人都会遇到这个小小的坎,的确有点麻烦,当时我也折腾了好一半天,如果你能看到这篇博客,说明你和我当初也是一样的焦灼,如果你想解决掉这个小小的坎 ...

  9. Android ListView异步载入图片乱序问题,原因分析及解决方式

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/45586553 在Android全部系统自带的控件其中,ListView这个控件算是 ...

随机推荐

  1. JS-006-表格元素操作

    直接上菜咯... 以下为 HTML 表格源码: <html> <head> <meta http-equiv="Content-Type" conte ...

  2. oracle视图V$BH && X$BH的使用列子

    1创建一个测试表,test,并且插入10000行数据:    SQL>  create table test (id int); SQL> begin      2  for i in 1 ...

  3. SQL Server 2012 新的分页函数 OFFSET & FETCH NEXT

    DECLARE @page INT, @size INT;select @page = 300, @size = 10 SELECT *FROM gpcomp1.GPCUSTWHERE company ...

  4. How to delete expired archive log files using rman?

    he following commands will helpful to delete the expired archive log files using Oracle Recovery Man ...

  5. 在定位中,如何清除已经设置好的bottom

    这个东西其实很简单,将bottom设auto,便可在设置top的值,那么具体的应用是什么?目前一个是用于全屏,二是用于输入框在手机端的下半屏(被输入发遮挡问题)? 例子如下 css代码: * { ma ...

  6. SqlServer基础:约束

    为了减少输入错误和保证数据库数据的完整性,可以对字段设置约束,例如考试成绩,其范围应该为0-100.约束是为了保证数据的完整性而实现的一套机制,约束包括:主键约束.外键约束.Unique约束.Chec ...

  7. RedHat6.6安装Oracle11gR2

    RedHat6.6安装Oracle11gR2 一.Centos6.6的安装配置 1-       选择安装模式 2-       选择“skip”,跳过检查. 3-       选择“下一步” 4-  ...

  8. Vue.2.0.5-混合

    基础 混合是一种灵活的分布式复用 Vue 组件的方式.混合对象可以包含任意组件选项.以组件使用混合对象时,所有混合对象的选项将被混入该组件本身的选项. 例子: // 定义一个混合对象 var myMi ...

  9. CentOS 学习笔记

    整理基础的CentOS常用命令 http://os.51cto.com/art/201003/190801.htm 在Hyper-V中的CentOS虚拟机中使用网络 http://blog.earth ...

  10. HBase -- 基于HDFS的开源分布式NoSQL数据库

    HBase(Hadoop Database)是一个高可靠性.高性能.面向列.可伸缩的分布式存储系统,我们可以利用HBase技术在廉价的PC上搭建起大规模结构化存储集群.同Google的Bigtable ...