ListView乱谈之ListView的布局
来源:互联网 发布:网络女主播真实收入 编辑:程序博客网 时间:2024/05/18 02:16
本来预备写一篇博客的,写着写着发现要想细细写起来还是要很大篇幅,所以就预计写三篇博客。本篇主要是写ListView的布局,相对来说是本篇篇幅不是很大,其实对于android高手来说ListView的布局他们应该很容易就能知道其原理,不过还是准备把我的心得写出来,有不足和错误之处欢迎批评吐槽,批评吐槽过后再给指点一二。
ListView的布局就像在我之前实现的简单的横向ListView那样(详情点击此处),核心方法就是layout(left,top,right,bottom)方法的调用,该方法参数可以用如下图来说明:
其实通过这个图不难想象出让Adapter对象里getView方法所返回的View一个个竖直排列的思想很简单:在ListView高度允许的范围内,循环遍历Adapter中的ItemView,对该View进行测量并通过layout方法布局到ListView中去;然后取Adapter中的下一个position的View(在此称之为nextView),通过相应的位置计算,让nextView布局在上一个View的下面,到此完成布局的过程。上面所说的相应的位置计算,主要是改变每个ItemView的layout方法中第二个参数(top)的值。这个值每次递增的量(或者说下一个Itemview的top值)为:preItemView.getBottom() + mDividerHeight(该变量为ListView中ItemView之间的间隔).
简单的图例:
ListView的布局类型(layoutMode)有好几个,这里就从自上而下的布局开始讲起,布局涉及到的方法调用简单脉络可以表示为layoutChildren()--->fillFromTop(nextTop )-->fillDown(position,nextTop)-->makeAndaddView(position, nextTop,....)-->setupChild(child, position, nextTop, .., ......)-->view.layout(left,top,right,bottom);通过这个方法脉络可以看出nextTop一直在随着这些方法传递着(貌似是废话)。其中方法position代表着Adapter中第position位置的那个ItemView,该参数最终在makeAndAddView方法中使用,其使用也很简单: child = obtainView(position, mIsScrap);只要简单的阅读源码 就知道obtainView方法中调用了adapter.getView(position,convertView,parent)方法。
private View fillDown(int pos, int nextTop) { View selectedView = null; //获取listView的高度 int end = (mBottom - mTop); if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { end -= mListPadding.bottom; } /*** 循环遍历对当前child布局,并计算下一个child的layout的top值 */ <pre name="code" class="java"> while (nextTop < end && pos < mItemCount) { // is this the selected item? boolean selected = pos == mSelectedPosition; //将此child添加并布局到ListView中 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); //计算下一个child的layout方法的top值 nextTop = child.getBottom() + mDividerHeight; if (selected) { selectedView = child; } //该参数用来表示Adapter中下一个child的位置,也就是getView方法中第一个参数 pos++; }
最终的布局是在setupChild方法中进行的:该方法大整体上分成两个部分,一是对先itemView进行测量(详细点击此处),二是对测量过后的View通过layout方法布局到ListView中(点击这里是关于layout的一个简单的应用);大体代码如下:
private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) { //此处省略若干代码 // 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; } //讲child 添加到ViewGroup的数组View[] mChildren中去 addViewInLayout(child, flowDown ? -1 : 0, p, true); } //进行测量 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(); //此处的y就是上文所说的nextTop final int childTop = flowDown ? y : y - h; if (needToMeasure) { final int childRight = childrenLeft + w; final int childBottom = childTop + h; //此处正是child进行布局的真正地方 child.layout(childrenLeft, childTop, childRight, childBottom); } else { child.offsetLeftAndRight(childrenLeft - child.getLeft()); child.offsetTopAndBottom(childTop - child.getTop()); } //此处省略若干代码 }
到此为止,ListView的实现布局的原理就简单的写完了。不过本文到此并未结束,还需要讲一些其他的东西,比如重复利用的View以及关于ListVIew的一些小细节。通过上面的代码可发现,fillDown里面有一个方法的while循环,如下:
while (nextTop < end && pos < mItemCount) { boolean selected = pos == mSelectedPosition; //将此child添加并布局到ListView中 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); //计算下一个child的layout方法的top值 nextTop = child.getBottom() + mDividerHeight; if (selected) { selectedView = child; } //该参数用来表示Adapter中下一个child的位置,也就是getView方法中第一个参数 pos++; }while循环的一个条件就是nextTop<end,这个end值代表着什么呢?源码中是end = (mBottom - mTop);,也就是ListView的高度,严格的来说是ListView可以用来布局的高度(有padding值),而nextTop的意思就不用多说了。通过nextTop<end这个限制可以知道,在ListView中并不是把Adapter中全部的ItemView一次性全部布局到ListVIew中,而是屏幕根据ItemView的高度能显示多少个ItemView就先add多少个ItemView到ListView里面去。所以ListView中getCount()和getChildCount()是两个完全不同的概念:
getChildCount返回的是ListView中在屏幕中可看到的itemView的个数
getCount()返回的是mItemCount,该变量是在调用setAdapter等方法的时候通过 mItemCount = mAdapter.getCount();很简单就是Adapter里面有多少个Item,mItemCount就等于多少。
同时,我们知道getFirstVisiablePosition():获取在页面中第一个可见的View(item),也就是adapter的getView方法参数中参数position对应的值,在ListView的父类AdapterView中用mFirstPosition变量来表示,哪怕只有部分的View显示出来,也被当做第一个可见的view;getLastVisiablePosition():获取在页面中最有一个可见的View(item),哪怕只有部分的View显示出来也被当做最有一个可见的View,所以
getLastVisiablePosition() == getFirstVisiablePosition()+getChildCount()-1
在android中addView的时候,最终通过addViewInLayout调用了ViewGroup里面addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout)方法(该方法又调用了addInArray方法),ViewGrouup里面提供一个View的数组mChildren,在addView方法中调用了addVew(view view,index),index默认传的是-1,表明添加到View数组mChildren最后面,当index为正数的时候就把该View插入到数组中的index的位置,相应的View数组mChildren里面的元素后移;也就说说本质上ViewGroup里面的addView系列重载方法其实就是对mChilderen这个View类型的数据进行的数组插入操作。
在setupChild方法中就调用了 addViewInLayout(child, flowDown ? -1 : 0, p, true)把Adapter中的View添加到了ViewGroup的数组中去因为在代码中有if(index<0){index=mChildren}这个处理。按照我们上面的探讨顺序,flowDown是true:也就是addInArray参数的index为-1;
private void addInArray(View child, int index) { View[] children = mChildren; final int count = mChildrenCount;//获取ViewGroup final int size = children.length; if (index == count) { if (size == count) { mChildren = new View[size + ARRAY_CAPACITY_INCREMENT]; System.arraycopy(children, 0, mChildren, 0, size); children = mChildren; } children[mChildrenCount++] = child; } else if (index < count) { if (size == count) { mChildren = new View[size + ARRAY_CAPACITY_INCREMENT]; System.arraycopy(children, 0, mChildren, 0, index); System.arraycopy(children, index, mChildren, index + 1, count - index); children = mChildren; } else { System.arraycopy(children, index, children, index + 1, count - index); } children[index] = child; mChildrenCount++; if (mLastTouchDownIndex >= index) { mLastTouchDownIndex++; } } else { throw new IndexOutOfBoundsException("index=" + index + " count=" + count); } }
因为getChildAt(int index)就是从数组mChildren获取返回对应索引的View:return mChildren[index],所以需要注意的是:在ListView中,使用getChildAt(index)的取特定位置的View的时候,index的取值范围是 index>=getFristVisiblePosition()&&index<==getlastVisiablePosition();超出此范围的话getChildAt会返回null;
另外AdapterView里面mSelectedPosition这个变量,代表着当前ListView中选中的ItemView所在的位置,对应的是该ItemView在getView中position的值);而AbsListView中的方法getSelectedView返回也是mChildren数组里面对应位置的View,所以这样的话getSelectedView返回的View=mChildren[mSelectionPosition-mFirstPosition]就不难理解了。
public View getSelectedView() { if (mItemCount > 0 && mSelectedPosition >= 0) { return getChildAt(mSelectedPosition - mFirstPosition); } else { return null; } }public View getChildAt(int index) { if (index < 0 || index >= mChildrenCount) { return null; } return mChildren[index]; }
到此位置,ListView的布局就算是告一段落,通过读取里面的代码加深理解和学到不少的知识,写了这么多貌似有点啰嗦,
简单总结一下:
1)ListView的layout只是循环遍历Adapter中的View,通过累加layout方法的top参数的值来把一个个itemView 布局我们所见到的的那些效果
2)每次布局的时候,只是向ListView的mChildren数组中(该数组在ListView的父类ViewGroup中定义)添加Adapter中的部分ItemView,而不是全部。getChildAt方法和getSelectedView方法都是从mChildren数组中获取到对应的View返回之。
最后丢一个简单的疑问:
1)既然每次addView的时候不把全部的ItemView添加完,那么其余的itemView是什么时候,怎么添加进来的呢?
2)在添加新的itemView之前或者之后对mChildren数组都做了怎样的操作,这种操作的时机和目的是什么?
这些问题将在下一篇博客:《ListIView乱谈之ListView的滚动》详细解答
- ListView乱谈之ListView的布局
- ListView乱谈之ListView的滚动原理
- ListView乱谈之ListView中View重用的简单解析
- android之ListView布局
- android之ListView布局
- android布局之listview
- android之ListView布局
- android之ListView布局
- Android布局之ListView
- android学习之ListView布局的学习
- 自定义listview的布局
- ListView里的布局
- listview的布局问题
- 多种布局的ListView
- 复杂布局的ListView
- 带布局的ListView
- ListView的多布局
- ListView的多布局
- Connection的方法介绍
- SPOJ NUMTRYE Number Theory (Easy) (pollard_rho分解质因数)
- org.apache.poi.ss.usermodel 类操作excel数据遗漏
- c++设计模式----解释器模式interpreter
- POJ 2886 线段树
- ListView乱谈之ListView的布局
- struts2 Map<String,Object>session心得 浅析
- Listview的自定义Adapter
- Codeforces Round #321 Kefa and Watch(字符串哈希+线段树)
- MarkdownPad 安装使用
- string与char*和char[]之间的相互转换
- request.getInputStream中文乱码解决方案
- iOS开发中 使用XML和JSON对本地文件进行解析
- hihoCoder 1236 Scores 五维偏序 (分块 + bitset)