最近在作重庆掌厅的项目,发现使用ViewGroup
包裹的子view
,常常子view
的高度测量不许,为了了解这个问题的本质缘由,简单写了几个demo做为演示,自定义View
是比较重要的,尤为是布局和测量流程
测量和布局的方法实际上是比较少的,一个是 onMeasure
,一个是 onLayout
,这个是布局的核心所在。
下面咱们就开始布局过程:java
view
的位置和尺寸 通俗来讲就是:计算每一个 view
的尺寸以及相对于他们的父view
的相对位置,目的是帮助咱们写布局文件的难。ide
View
绘制和触摸反馈作支持1. 测量过程:
从根 View
递归调用每个子View
的 measure()
方法。让每一个子view
进行自我测量,每次测量以后,它会算出每一个子view
,都会算出本身的指望尺寸局,根据本身指望的尺寸得出子view,实际尺寸。布局
2. 布局过程:
从根 View
递归调用到每一级的 layout()
方法,把测量过程得出的子view
位置和尺寸传给子 view
,子view
保存。ui
3. 为何要有两个流程呢?
主要是测量过程比较复杂,可能测量某个子view
,直接获得它的位置和尺寸,可是有的时候,你可能须要测量屡次,你一次不行可能须要测量屡次。spa
运行前,开发者在 xml
文件写入对View
的布局要求layout_xxx
。code
父 view
在本身的onMeasure
中,根据开发者在xml
中写对子view
的要求,和本身的可用空间,得出子view的具体尺寸要求。xml
子view
在本身的onMeasure()
中根据本身的父view
的特性算出本身的指望值。递归
viewGroup
,还会在这里调用每一个子view
的measure()
进行测量。父view
在子view
计算出指望尺寸后,得出子view
的实际尺寸和位置。
开发
子view
在本身的layout()
方法中,将view
传进本身的实际尺寸和位置保存。get
viewGroup
,还会在onLayout
里面每一个字view
的onLayut()
把它们尺寸位置传给它们。extend
已有的view
,简单修改他们的尺寸public class SquareImageView extends AppCompatImageView {
public SquareImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
/** 1. 重写`onMeasure()` */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 2. ⽤ getMeasuredWidth() 和 getMeasuredSize() 获取到测量出的尺寸
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
// 3. 计算最终的尺寸
int size = Math.max(measuredWidth, measuredHeight);
// 4.用 setMeasuredDimension 把最终的结果保存起来
setMeasuredDimension(size, size);
}
}
复制代码
须要注意的是在自定义view
里面是不能用 onLayout
去获取 view
宽高的,由于父view
可能会被测量屡次,拿到的宽高并不必定是真实的宽高。
咱们须要获取的是测量阶段算出来的view
的宽高,getWidth
和getHeight
其实是在layout
里面获取的宽高。即右 - 左边距;那个实际的宽度和高度
通常状况下,测量的结果都同样,你的宽度和高度在测量过程是拿不到的,还没布局,因此保险起见只能用getMeasureWidth
和getMeasureHeight
。就至关于你论文的初稿。真正定稿是getWidth
和getHeight
。
因此只要遵循一个原则,在绘制阶段,只能用getMeasureWidth
和getMeasureHeight
,在触摸反馈和布局阶段只能用getWidth
和getHeight
。
View
的尺寸onMeasure
resolveSize()
或 resolveSizeAndState()
修正结果public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
// 1.MeasureSpec.getMode(measureSpec) 和 MeasureSpec.getSize(measureSpec) 取出对本身的尺寸类型和具体尺寸
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
// 若是measure spec 的model是 AT_MOST,表示view对子view的尺寸只限制了上限
if (specSize < size) {
// 若是计算出的size 不大于 spec 的size,而且在 resolveSizeAndState 会添加标志 MEASURED_STATE_TOO_SMALL 这个能够辅助父view的测量
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
// 若是模式是`EXACTLY`,表示父view对子view作出了精确的限制,因此就放弃计算的size直接选择measureSpec的size
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
// 若是measure spec 的 model 是 UNSPECIFIED , 表示父 view 对 子 view 没有任何尺寸限制,因此直接选用计算出来的size,忽略 spec 的 size
default:
result = size;
}
return result | (childMeasuredState & MEASURED_STATE_MASK);
}
复制代码
setMeasureDimension(width,height)
保存结果@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int size = (int) ((PADDING + radius) * 2);
int measuredWidth = resolveSize(size, widthMeasureSpec);
int measuredHeight = resolveSize(size, heightMeasureSpec);
setMeasuredDimension(measuredWidth, measuredHeight);
}
复制代码
Layout
: 重写onMeasure()
和 onLayout()
: TagLayout
时间关系,明天总结~