上一篇:
自定义View: 一般继承自View,SurfaceView或其他的View,不包含子View。
自定义ViewGroup: 一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout,包含有子View。
自定义View绘制流程函数调用链(简化版)
1、构造函数
//一般用于new一个View时使用,
public CircleView(Context context) {
this(context, null);
}
//一般用于xml中定义的view,xml定义的属性会通过attrs传递过来
public CircleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) //API21才出,一般可以不用,直接删除
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
2、onMeasure()
前面我们在View绘制流程中说了,最终是为了确定mMeasuredWidth、mMeasuredHeight,用于onLayout中的mRight和mBottom。
-
如果我们把对view的宽高进行改变,一定要手写setMeasuredDimension()。
-
另外onMeasure(widthMeasureSpec, heightMeasureSpec)中一定要setMeasuredDimension(),否则会报错。
onMeasure() did not set the measured dimension by calling setMeasuredDimension()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec); //可以屏蔽,但是一定要setMeasuredDimension()
int widthsize = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
int widthmode = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
int heightsize = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
int heightmode = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式
setMeasuredDimension(widthsize, heightsize);
}
2.1、MeasureSpec
MeasureSpec是View中的一个子类,代表的是测量规格:32位的int类型,高两位是mode,低30位是size
Mode有三种状态:
- UNSPECIFIED :父控件没有给子view任何限制,子View可以设置为任意大小。linearLayout/scrollView (随心所欲)
- EXACTLY:父View有确切的大小,子View (match_parent、dp/px)必须是这个范围内 (固定大小)
- AT_MOST:父View为子view指定了一个范围,在这个范围内,子View可以尽可能的大,wrap_content(受限制的)
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT; // 11 0000000000000(后接30个0)
public static final int UNSPECIFIED = 0 << MODE_SHIFT; // 00 000000000000000(后接30个0)
public static final int EXACTLY = 1 << MODE_SHIFT; // 01 000000000000000(后接30个0)
public static final int AT_MOST = 2 << MODE_SHIFT; // 10 000000000000000(后接30个0)
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode; //二进制的加法
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//得到测量模式
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
//得到测量尺寸
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
3、onSizeChanged()
-
在View.java中有5个地方会调用onSizeChanged()
public final void setTop(int top) public final void setBottom(int bottom) public final void setLeft(int left) public final void setRight(int right) protected boolean setFrame(int left, int top, int right, int bottom) //是在view#layout(l,t,r,b)中回调的
View#onSizeChanged()
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
}
4、onLayout()
自定义View中不用到,因为view没有子View。
在自定义ViewGroup中,onLayout()一般是循环取出子View,然后经过计算得出各个子View位置的坐标值,然后用以下函数设置子View位置。
child.layout(l, t, r, b);
l, t, r, b是相对父View的距离。
5、onDraw()
onDraw是实际绘制的部分,使用的是Canvas绘图。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 1; i <= 5; i++) {
rect.set(margin + (i - 1) * (width + margin), margin,
margin + (i - 1) * (width + margin) + width, margin + width);
canvas.drawRect(rect, paint);
canvas.drawPoint(margin + width / 2 + (i - 1) * (width + margin), margin + width / 2, paint);
}
}
这画的是个什么东西呢?先看下效果图:5个框框,每个框框中间画个点。
鬼知道我什么要画个这个,还命名CircleView,也许一开始我是想画个圆的。
自行在xml中引用一下:(根据自己的包名进行修改)
<com.example.administrator.myapplication.draw.CircleView //根据自己的包名进行修改
android:layout_width="match_parent"
android:layout_height="100dp" />
把完整代码贴一下:
public class CircleView extends View {
private Paint paint = new Paint();
private int width = 100;
private int margin = 40;
private Rect rect;
public CircleView(Context context) {
this(context, null);
}
public CircleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
paint.setColor(Color.GRAY);
paint.setStrokeWidth(4);
paint.setStyle(Paint.Style.STROKE);
rect = new Rect();
ContextCompat.getColor(context, R.color.colorAccent);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 1; i <= 5; i++) {
rect.set(margin + (i - 1) * (width + margin), margin,
margin + (i - 1) * (width + margin) + width, margin + width);
canvas.drawRect(rect, paint);
canvas.drawPoint(margin + width / 2 + (i - 1) * (width + margin), margin + width / 2, paint);
}
}
}