View的绘制流程是从ViewRoot的PerformTraversals方法开始的,它经过measur.layout和draw三个过程才能将一个view绘制出来,其中measure用来测量View的宽度,layout用来确定View在父容器中放置的位置,draw负责将View绘制在屏幕上面。
- onMeasure()
MeasureSpec
通过MeasureSpec可以帮助我们测量View
MeasureSpec是一个32位的Int值,高2位是测量的模式,低30位为测量的大小。在计算中使用位运算原因是为了提高并优化效率。
测量模式
EXACTLY 精准模式 当控件的width或height为具体数值的时候或者match_parent 时系统用的是EXACTLY模式
AT_MOST 即最大模式 控件的width或height为wrap_content 随着控件的子控件或者内容变化而变化
UNSPECIFIED 不指定其大小测量的模式,通常在绘制自定义 View时才会使用
默认 EXACTLY 如果想使用 wrap_content 必须重写 onMeasure();
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
在suprer.onMeasure()方法点击进去看后可以发现最后调用了 setMeasuredDimensionRaw()方法把宽和高设了进去
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
所以可以这么重写onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
width = MeasureWidth(widthMeasureSpec);
height = MeasureHeight(heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int MeasureHeight(int heightMeasureSpec) {
int result;
int size = MeasureSpec.getSize(heightMeasureSpec);
int mode = MeasureSpec.getMode(heightMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 200;//默认的值
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(size, result);
}
}
return result;
}
private int MeasureWidth(int widthMeasureSpec) {
int result;
int size = MeasureSpec.getSize(widthMeasureSpec);
int mode = MeasureSpec.getMode(widthMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 200;
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(size, result);
}
}
return result;
}
- onLayout()
onLayout方法是ViewGroup中子View的布局方法,用于放置子View的位置。
放置子View很简单,只需在重写onLayout方法,然后获取子View的实例,调用子View的layout方法实现布局。
在实际开发中,一般要配合onMeasure测量方法一起使用。
下面是一个自定义ViewGroup的Demo,用onLayout和layout实现子View的水平放置,间隔是20px
//子View的水平间隔private final static int padding = 20 ;@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
// 动态获取子View实例
for (int i = 0, size = getChildCount(); i < size; i++) {
View view = getChildAt(i);
// 放置子View,宽高都是100
view.layout(l, t, l + 100, t + 100);
l += 100 + padding;
}
}
- onDraw()
根据名字你就能够判断出,在这里才真正地开始对视图进行绘制。
ViewRoot中的代码会继续执行并创建出一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工作。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
- 在回调父类方法前实现自己的逻辑,对View来说既是在绘制文本内容前
- 在回调父类方法后实现自己的逻辑,对View来说既是绘制文本内容后
- View中其他重要的方法
- onFinishInflate() 从Xml中加载组件后回调
- onSizeChanger(); 组件大小改变时回调
- onMeasure(); 回调该方法进行测量
- onLayout(); 回调该方法来确定显示的位置
- onTouchEvent(); 监听触摸事件时回调