-
一、坐标系
-
分类和流程
几个重要的函数
1.构造函数
构造函数是View的入口,可以用于初始化一些的内容,和获取自定义属性。
View的构造函数有四种重载分别如下:
public void SloopView(Context context) {}
public void SloopView(Context context, AttributeSet attrs) {}
public void SloopView(Context context, AttributeSet attrs, int defStyleAttr) {}
public void SloopView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {}
有三个参数的构造函数中第三个参数是默认的Style,这里的默认的Style是指它在当前Application或Activity所用的Theme中的默认Style,且只有在明确调用的时候才会生效,以系统中的ImageButton为例说明:
public ImageButton(Context context, AttributeSet attrs) {
//调用了三个参数的构造函数,明确指定第三个参数
this(context, attrs, com.android.internal.R.attr.imageButtonStyle);
}
public ImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
//此处调了四个参数的构造函数,无视即可
this(context, attrs, defStyleAttr, 0);
}
排除了两个之后,只剩下一个参数和两个参数的构造函数,他们的详情如下:
//一般在直接New一个View的时候调用。
public void SloopView(Context context) {}
//一般在layout文件中使用的时候会调用,关于它的所有属性(包括自定义属性)都会包含在attrs中传递进来。
public void SloopView(Context context, AttributeSet attrs) {}
以下方法调用的是一个参数的构造函数:
//在Avtivity中
SloopView view new SloopView(this);
以下方法调用的是两个参数的构造函数:
//在layout文件中 - 格式为: 包名.View名
<com.sloop.study.SloopView
android:layout_width"wrap_content"
android:layout_height"wrap_content"/>
2.测量View大小(onMeasure)
View的大小不仅由自身所决定,同时也会受到父控件的影响,为了我们的控件能更好的适应各种情况,一般会自己进行测量。
测量View大小使用的是onMeasure函数,我们可以从onMeasure的两个参数中取出宽高的相关数据:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthsize = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
int widthmode = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
int heightsize = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
int heightmode = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式
}
从上面可以看出 onMeasure 函数中有 widthMeasureSpec 和 heightMeasureSpec 这两个 int 类型的参数, 毫无疑问他们是和宽高相关的, 但它们其实不是宽和高, 而是由宽、高和各自方向上对应的测量模式来合成的一个值:
测量模式一共有三种, 被定义在 Android 中的 View 类的一个内部类View.MeasureSpec中:
模式 | 二进制数值 | 描述 |
---|---|---|
UNSPECIFIED | 00 | 默认值,父控件没有给子view任何限制,子View可以设置为任意大小。 |
EXACTLY | 01 | 表示父控件已经确切的指定了子View的大小。 |
AT_MOST | 10 | 表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小。 |
用 MeasureSpec 的 getSize是获取数值, getMode是获取模式。
注意:
如果对View的宽高进行修改了,不要调用 super.onMeasure( widthMeasureSpec, heightMeasureSpec); 要调用 setMeasuredDimension( widthsize, heightsize)这个函数。
3.确定View大小(onSizeChanged)
这个函数在视图大小发生改变时调用。
因为View的大小不仅由View本身控制,而且受父控件的影响,所以我们在确定View大小的时候最好使用系统提供的onSizeChanged回调函数。
onSizeChanged如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
四个参数,分别为 宽度,高度,上一次宽度,上一次高度。
只需关注 宽度(w), 高度(h) 即可,这两个参数就是View最终的大小。
4.确定子View布局位置(onLayout)
确定布局的函数是onLayout,它用于确定子View的位置,在自定义ViewGroup中会用到,他调用的是子View的layout函数。
在自定义ViewGroup中,onLayout一般是循环取出子View,然后经过计算得出各个子View位置的坐标值,然后用以下函数设置子View位置。
child.layout(l, t, r, b);
5.绘制内容(onDraw)
onDraw是实际绘制的部分,也就是我们真正关心的部分,使用的是Canvas绘图。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
6.对外提供操作方法和监听回调
自定义完View之后,一般会对外暴露一些接口,用于控制View的状态等,或者监听View的变化.
-
三、Canvas
操作类型 |
相关API |
备注 |
---|---|---|
绘制颜色 |
drawColor, drawRGB, drawARGB |
使用单一颜色填充整个画布 |
绘制基本形状 |
drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc |
依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧 |
绘制图片 |
drawBitmap, drawPicture |
绘制位图和图片 |
绘制文本 |
drawText, drawPosText, drawTextOnPath |
依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字 |
绘制路径 |
drawPath |
绘制路径,绘制贝塞尔曲线时也需要用到该函数 |
顶点操作 |
drawVertices, drawBitmapMesh |
通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用 |
画布剪裁 |
clipPath, clipRect |
设置画布的显示区域 |
画布快照 |
save, restore, saveLayerXxx, restoreToCount, getSaveCount |
依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数 |
画布变换 |
translate, scale, rotate, skew |
依次为 位移、缩放、 旋转、错切 |
Matrix(矩阵) |
getMatrix, setMatrix, concat |
实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。 |
private Paint mPaint = new Paint();初始化画笔
private void initPaint() {
mPaint.setColor(Color.BLACK);//设置画笔颜色
mPaint.setStyle(Paint.Style.FILL);//设置画笔模式为填充
mPaint.setStrokeWidth(10f); //设置画笔宽度为10px
}
public SloopView(Context context, AttributeSet attrs) {
super(context, attrs); initPaint();
}
绘制点: canvas.drawPoint(200, 200, mPaint);
绘制线: canvas.drawLine(300,300,500,600,mPaint);
绘制矩形: canvas.drawRect(100,100,800,400,mPaint);
Rect rect = new Rect(100,100,800,400); canvas.drawRect(rect,mPaint);
RectF rectF = new RectF(100,100,800,400); canvas.drawRect(rectF,mPaint);
绘制圆: canvas.drawCircle(500,500,400,mPaint);
绘制圆弧: public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint){}
四、注意事项:
1、在 xml中创建了一个view时,所有在xml中声明的属性都会被传入到view的构造方法中的AttributeSet类型的参数当中。 通过调用Context的obtainStyledAttributes()方法返回一个TypedArray对象。然后直接用TypedArray对象获取自定义属性的值。TypedArray对象是共享的资源,所以在获取完值之后必须要调用recycle()方法来回收。
2、自定义控件的属性发生改变之后,控件的样子也可能发生改变,在这种情况下就需要调用invalidate()方法让系统去调用view的onDraw()重新绘制。
3、在onDraw方法中开始绘制之前,应该让画笔Paint对象的信息初始化完毕。这是因为View的重新绘制是比较频繁的,这就可能多次调用onDraw,所以初始化的代码不应该放在onDraw方法里。
参考:http://www.gcssloop.com/customview/CustomViewIndex