View 的工作原理主要包含 View 的三大流程Measure、Layout和Draw,即测量、布局、绘制。
measure过程
View:
通过measure方法就完成了其测量。measure()是final的方法,子类无法重写此方法,measure()中会调用View的onMeasure(widthMeasureSpec, heightMeasureSpec)方法并传递MeasureSpec给他,MeasureSpec在上一篇博文已经讲过。
onMeasure()会执行
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
方法设置View宽/高的测量值
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
getDefaultSize()
作用是返回一个默认的值,如果MeasureSpec没有强制限制的话则使用提供的大小,否则在允许范围内可任意指定大小,第一个参数size为提供的默认大小,第二个参数为测量的大小。
直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时自身大小,否则在布局中使用就相当于使用match_parent。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);//都为最大模式时(wrap_content)
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mHeight);
}
}
上面的代码中给View指定了一个默认的内部宽/高,mWidth和mHeight,在wrap_content时设置默认的大小,非wrap_content时沿用系统的测量值。
ViewGroup:
与View的过程不同,除了完成自己的测量过程,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。ViewGroup是一个抽象类,没有重写View的onMeasure,不同的ViewGroup子类有不同的布局特性,这导致它们的测量细节各不相同。
layout过程
这个方法是用于给视图进行布局的,也就是确定视图的位置。ViewRoot的performTraversals()方法会在measure结束后继续执行,并调用View的layout()方法来执行此过程。layout方法确定View本身位置,而onLayout方法会确定所有子元素的位置。
layout完成后可以通过getWidth和getHeight获取最终宽高。
draw过程
draw的作用是将View绘制到屏幕上,其过程如下几步:
-
drawBackground(canvas);
绘制背景 -
onDraw(canvas);
绘制自己本身 -
dispathDraw(canvas);
绘制children -
onDrawScrollBars(canvas);
绘制装饰
dispathDraw会遍历调用所有子元素的draw方法,如此draw事件就一层一层传递下去。
自定义View
自定义View分类:
- 继承View重写onDraw方法(需自己支持wrap_content、padding)
- 继承特定的View(比如TextView等,不需要自己支持wrap_content、padding)
- 继承特定的ViewGroup(如LinearLayout)
- 继承ViewGroup派生特殊的Layout(比较复杂)
继承View重写onDraw
在布局文件使用自定义属性:首先在values新建attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color"/>
</declare-styleable>
</resources>
在布局文件添加
xmlns:app="http://schemas.android.com/apk/res-auto"
绘制一个圆的过程:
public class CircleView extends View {
private int mColor = Color.BLACK;//圆的颜色
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//定义画笔
public CircleView(Context context) {
super(context);
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);//加载自定义属性
mColor = a.getColor(R.styleable.CircleView_circle_color, Color.BLACK);//默认值为蓝色
a.recycle();//释放资源
init();
}
private void init() {
mPaint.setColor(mColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, 200);//都为最大模式时(wrap_content)
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(200, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, 200);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;
int radius = Math.min(width, height) / 2;
canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint);
}
}
网友评论