自定义View布局
1.确定每个View的位置和尺寸
2.作用:为绘制和触摸范围做支持
1.对于绘制:知道自己需要在哪里绘制。
2.对于触摸反馈:知道用户的点是在哪里。
自定义View布局的工作内容
自定义View的工作分为两个阶段:测量阶段和布局阶段
测量流程:从上到下递归调用每个View或者ViewGroup的measure()方法,测量他们的尺寸并计算他们的位置。
布局阶段:从上到下递归地调用每个View或者ViewGroup的layout()方法,把测量得到的他们的尺寸和位置赋值给他们。
View或者ViewGroup的布局过程
1.测量阶段:measure()方法被父View调用,在measure()中做一些准备和优化工作后,调用onMeasure()来进行实际的自我测量。onMeasure()做的事情,在View和ViewGroup中是不一样的。
1.View:View在onMeasure()中会计算出自己的尺寸然后保存;
2.ViewGroup:ViewGroup在onMeasure()中会调用所有子View的measure()让他们进行自我测量,并根据子View计算出的期望尺寸来计算出他们的实际尺寸(实际上99.99%的父View都会使用子View给出的期望尺寸来作为实际尺寸),然后保存。
2.布局阶段:layout()方法被父View调用,在layout()中他会保存父View传进来的自己的位置和尺寸,并且调用onLayout()来进行实际的内部布局。onLayout()做的事,View和ViewGroup也不一样:
1.View:由于没有子View,所以View的onLayout()什么也不需要做。
2.ViewGroup:ViewGroup在onLayout()中会调用自己的所有的子View的layout()方法,把它们的尺寸和位置传递给他们,并且让他们自己完成自己内部的布局。
布局过程自定义的方式
布局过程自定义的方式有三类
1.重写onMeasure()来修改已有的View的尺寸。
2.重写onMeasure()来全新定制自定义View的尺寸。
3.重写onMeasure()和onLayout()来全新定制自定义ViewGroup的内部布局。
具体做法
继承已有的 View,简单改写它们的尺⼨寸:重写 onMeasure():SquareImageView
1. 重写 onMeasure()
2. ⽤用 getMeasuredWidth() 和 getMeasuredSize() 获取到测量量出的尺⼨寸 3. 计算出最终要的尺⼨寸
4. ⽤用 setMeasuredDimension(width, height) 把结果保存
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int size = Math.min(width, height);
setMeasuredDimension(size, size);
}
对⾃自定义 View 完全进⾏行行⾃自定义尺⼨寸计算:重写 onMeasure():CircleView
重写 onMeasure()
计算出⾃自⼰己的尺⼨寸
⽤用 resolveSize() 或者 resolveSizeAndState() 修正结果
3.1resolveSize() / resolveSizeAndState() 内部实现(⼀一定读⼀一下代码,这个极少需要⾃自 ⼰己写,但⾯面试时很多时候会考):
3.1.1⾸首先⽤用 MeasureSpec.getMode(measureSpec) 和 MeasureSpec.getSize(measureSpec) 取出⽗父 对⾃自⼰己的尺⼨寸限制类型和具体限制 尺⼨寸;
3.1.2如果 measure spec 的 mode 是 EXACTLY,表示⽗父 View 对⼦子 View 的尺⼨寸做出 了了精确限制,所以就放弃计算出的 size,直接选⽤用 measure spec 的 size;
3.1.3如果 measure spec 的 mode 是 AT_MOST,表示⽗父 View 对⼦子 View 的尺⼨寸只限 制了了上限,需要看情况:
3.1.4.1如果计算出的 size 不不⼤大于 spec 中限制的 size,表示尺⼨寸没有超出限制, 所以选⽤用计算出的 size;
3.1.4.2而如果计算出的 size ⼤大于 spec 中限制的 size,表示尺⼨寸超限了了,所以选⽤用 spec 的 size,并且在 resolveSizeAndState() 中会添加标志 MEASURED_STATE_TOO_SMALL(这个标志可以辅助⽗父 View 做测量量和布 局的计算;
3.1.5如果 measure spec 的 mode 是 UNSPECIFIED,表示⽗父 View 对⼦子 View 没有任 何尺⼨寸限制,所以直接选⽤用计算出的 size,忽略略 spec 中的 size。-
使⽤用 setMeasuredDimension(width, height) 保存结果
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = (int) ((PADDING + RADIUS) * 2); int height = (int) ((PADDING + RADIUS) * 2); setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, 0), }
⾃自定义 Layout:重写 onMeasure() 和 onLayout():TagLayout
1. 重写 onMeasure()
1. 遍历每个⼦子 View,⽤用 measureChildWidthMargins() 测量量⼦子 View
• 需要重写 generateLayoutParams() 并返回 MarginLayoutParams 才能使⽤用 measureChildWithMargins() ⽅方法
• 有些⼦子 View 可能需要重新测量量(⽐比如换⾏行行处)
• 测量量完成后,得出⼦子 View 的实际位置和尺⼨寸,并暂时保存
protected void onMeasure(int widthMeasureSpec, int
heightMeasureSpec) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
Rect childBounds = childrenBounds[i];
// 测量量⼦子 View
measureChildWithMargins(child, widthMeasureSpec,
widthUsed,
heightMeasureSpec, heightUsed); // 保存⼦子 View 的位置和尺⼨寸
childBounds.set(childlLeft, childTop, childLeft
+ child.getMeasuredWidth(), chiltTop
+ child.getMeasuredHeight());
......
}
// 计算⾃自⼰己的尺⼨寸,并保存
int width = ...;
int height = ...; setMeasuredDimension(resolveSizeAndState(width,
widthMeasureSpec, 0),
resolveSizeAndState(height, heightMeasureSpec,
0)); }
• measureChildWidthMargins() 的内部实现(⼀一定读⼀一下代码,这个极少需要⾃自 ⼰己写,但⾯面试时很多时候会考):
通过 getChildMeasureSpec(int spec, int padding, int childDimension) ⽅方 法计算出⼦子 View 的 widthMeasureSpec 和 heightMeasureSpec,然后调 ⽤用 child.measure() ⽅方法来让⼦子 View ⾃自我测量量;
// ViewGroup.measureChildWithMargins() 源码 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp =
(MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec =
getChildMeasureSpec(int spec, int padding, int childDimension) ⽅方法的内部 实现是,结合开发者设置的 LayoutParams 中的 width 和 height 与⽗父 View ⾃自 ⼰己的剩余可⽤用空间,综合得出⼦子 View 的尺⼨寸限制,并使⽤用 MeasureSpec.makeMeasureSpec(size, mode) 来求得结果:
getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin
+ lp.rightMargin + widthUsed, lp.width);
final int childHeightMeasureSpec =
getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin
+ lp.bottomMargin + heightUsed, lp.height);
child.measure(childWidthMeasureSpec,
childHeightMeasureSpec);
}
getChildMeasureSpec(int spec, int padding, int childDimension) ⽅方法的内部 实现是,结合开发者设置的 LayoutParams 中的 width 和 height 与⽗父 View ⾃自 ⼰己的剩余可⽤用空间,综合得出⼦子 View 的尺⼨寸限制,并使⽤用 MeasureSpec.makeMeasureSpec(size, mode) 来求得结果: