一、自定义View三个步骤:onMeasure,onLayout,onDraw
onMeasure 一定要在onLayout之前,之后再onLayout
ViewGroup一般不用处理onDraw
二、onMeasure方法,测量布局
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
MeasureSpec
MeasureSpec 是View的一个内部类,他可以用一个int同时表示一个宽度或者高度的mode和size
一个int的32位,高两位表示mode,剩余30位表示size
mode 包含 UNSPECIFIED, EXACTLY, AT_MOST,其值的大小如下
private static final int MODE_SHIFT = 30;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
调用MeasureSpec.makeMeasureSpec(int size,int mode) 可以计算出合并后MeasureSpec值
三种模式的含义:UNSPECIFIED一般是类似ScrollView的高这种不确定值,EXACTLY一般是MatchParent和传入具体值的情况,AT_MOST一般是WRAP_CONTENT的情况,只有一个父布局限制他的最大值
实际情况会比上面复杂这里贴出关键代码
/**
* specMode是自定义容器的mode
* size是自定义容器的size
* childDimension 是子ViewLayoutParams的宽或者高
**/
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
Measure流程如下
- DecorView 调用measure(int widthMeasureSpec, int heightMeasureSpec)方法
- measure方法中,这里会处理measure缓存等问题,一般不需要覆写,默认会在这里调用onMeasure(int widthMeasureSpec, int heightMeasureSpec)
- 如果是ViewGroup容器,则会先剔除Padding和Margin,然后计算出child的MeasureSpec并调用子View的measure(int widthMeasureSpec, int heightMeasureSpec)方法。子view又会走相同的流程形成递归
- 计算测量结果后,在onMeasure中调用setMeasuredDimension(int measuredWidth, int measuredHeight),来设置测量后的宽高。
三、onLayout方法,定位View
非容器的View一般不用实现onLayout方法
@Override
protected void onLayout(int left, int top, int right, int bottom) {
super.onLayout(left, top, right, bottom);
}
这里从getPaddingLeft和getPaddingTop开始,遍历子View逐一计算它应该放置的left,right,top,bottom
调用子View的onLayout,子View逐级调用onLayout。
getWidth和getHeight要在onLayout结束后才能获取到值
四、onDraw方法,绘制具体的显示逻辑
一般容器View不需要实现onDraw方法
@Override
protected void onDraw(Canvas canvas) {
}
为了避免在动效类自定义View频繁调用onDraw方法,导致频繁GC,要提前在构造函数中初始化Paint,Path等对象,path可以利用reset方法使用, 不能在onDraw方法重复创建
canvas.restore可以回到上次canvas.save的状态
网友评论