首先了解些关于布局的知识:
LayoutParams
类:LayoutParams are used by views to tell their parents how they want to be laid out
,是告诉父布局它想如何展示;其属性match_parent, warp_content
都是相对于父布局来说的,父布局在measure
子布局的时候首先会获取子布局的LayoutParams:LayoutParams p = (LayoutParams) child.getLayoutParams();
之后结合父布局的widthMeasureSpec
和heightMeasureSpec
得到childWidthSpec, childHeightSpec
之后调用child.measure(childWidthSpec, childHeightSpec);
对child进行measure;ListView
通过getView
函数获得item view
,具体是通过函数:public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot);
的调用来生成view;inflate函数有三种调用方式:
1.view = inflater.inflate(R.layout.view_item, viewGroup, false); 2.view = inflater.inflate(R.layout.view_item, null); 3.view = inflater.inflate(R.layout.view_item, viewGroup, false);
对于listview支持两种1和2;
对于方式3是不支持的,如果使用这种调用方式,程序会出错,在inflate执行到下面的addview时出错:if (root != null && attachToRoot) { root.addView(temp, params); }
root就是ListView也就是AdapterView,很遗憾不支持addview;
throw new UnsupportedOperationException("addView(View, LayoutParams) " + "is not supported in AdapterView");
所以只能使用方式1和2;
这里的讲解可以参考这里使用方式1,那么item的顶层布局的
layout_width
和layout_height
都将失效,请无视它们吧,运行时啥作用也不会起;原因看下面代码 ①:final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { ...... // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } ...... // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } ...... return result;
如上
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
这里新鲜 出炉的view的layoutParams是null;而如果root == null
,则temp.setLayoutParams(params);
不 会执行,result = temp;
之后就返回了,listview在measure的时候对LayoutParams为null这种情况作 了判断:
ListView的layout_height
为warp_content
时:private void measureScrapChild(View child, int position, int widthMeasureSpec) { LayoutParams p = (LayoutParams) child.getLayoutParams(); if (p == null) { p = (AbsListView.LayoutParams) generateDefaultLayoutParams(); child.setLayoutParams(p); }
ListView的
layout_height
为match_parent
时,执行layout()时进行了判断:private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) { ...... AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams(); if (p == null) { p = (AbsListView.LayoutParams) generateDefaultLayoutParams(); } }
再来看下generateDefaultLayoutParams函数:
@Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { return new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0); }
ViewGroup.LayoutParams.WRAP_CONTENT就不用看了,很多时候没啥用,这也是root为null时我们的 item布局总是能横向铺满的原因了;
- root不为null的情况,则执行
params = root.generateLayoutParams(attrs);
这里就获得了当初设置的layout_width
和layout_height
;之后执行了temp.setLayoutParams(params);
用于addview以及addViewInner;所以item顶层width或height的具体数值,如:100dp
是有作用的; 很遗憾,item的布局顶层以及之下的
match_parent
或者warp_content
都没啥作用,因为ListView的childHeightSpec 完全是由子view的LayoutParams决定的;除非子view自己争气挣点高度空间,否则高度就是0,比如放个view,高度match_parent,布局预览的时候好好的,运行的时候就消失了,因为高度是0;例外是父层布局有具体的高度,则其子view的match_parent
和warp_content
仍然有意义;想一想也是这个道理,ListView
本来的高度就是不固定,你match_parent
,想match到哪里去;照例找下代码的证据:private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) { ...... AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams(); ...... 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); }
private void measureScrapChild(View child, int position, int widthMeasureSpec) { LayoutParams p = (LayoutParams) child.getLayoutParams(); ...... int lpHeight = p.height; ...... if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); }
MATCH_PARENT
值是-1WRAP_CONTENT
值是-2,所以lpHeight < 0
将执行childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
对于子view来说,就是看它自己的本事,自己有多大占多大;MeasureSpec.UNSPECIFIED
可以传递,子view如果还是match_parent
或者warp_content
,那还会执行childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
对于view的绘制流程讲解,推荐看这里