上一篇:
自定义View: 一般继承自View,SurfaceView或其他的View,不包含子View。
自定义ViewGroup: 一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout,包含有子View。
自定义View绘制流程函数调用链(简化版)
//一般用于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); }
前面我们在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); }
MeasureSpec是View中的一个子类,代表的是测量规格:32位的int类型,高两位是mode,低30位是size
Mode有三种状态:
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); } }
在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) { }
自定义View中不用到,因为view没有子View。
在自定义ViewGroup中,onLayout()一般是循环取出子View,然后经过计算得出各个子View位置的坐标值,然后用以下函数设置子View位置。
child.layout(l, t, r, b);
l, t, r, b是相对父View的距离。
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); } } }