之前讲了自定义View View的测量一部分,很好掌握吧?
但是View的测量只能控制View的大小,如果想定制外观,就要靠View的绘制。
View的绘制是要重写View 的 OnDraw方法
public class MyView extends View{
public MyView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas){
}
}
我们再看一下View类中 onDraw的源码
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}
是空的~ 也就是什么也没绘制
所以我们上一篇文章测试时候的View,就是一个没有图形的View。
可能有人会说,上一篇文章,最后明明显示是一个矩形!
但是实际上,呢只是因为View的边界包裹起来本身是个矩形,这和View的绘制是两码事
绘制,就是好像,View是一张矩形的纸,我们在这个纸上画画
View的绘制,我们使用Canvas ,Paint ,Path三个类就可以基本完成各种图形了
Canvas:
我直接先把Canvas提供的绘制方法放出来,大家看一下
方法签名 | 简要说明 |
---|---|
drawLine(float startX, float startY, float stopX, float stopY, Paint paint) | 绘制一条直线 |
drawLines(float[] pts, int offset, int count, Paint paint) | 绘制多条直线 |
drawCircle(float cx, float cy, float radius, Paint paint) | 绘制一个圆 |
drawOval(RectF oval, Paint paint) | 绘制一个椭圆 |
drawPath(Path path, Paint paint) | 根据路径绘制任意形状 |
drawPoint(float x, float y, Paint paint) | 绘制一个点 |
drawPoints(float[] pts, int offset, int count, Paint paint) | 绘制多个点 |
drawRect(float left, float top, float right, float bottom, Paint paint) | 绘制矩形 |
drawRoundRect(RectF rect, float rx, float ry, Paint paint) | 绘制圆角矩形 |
drawText(String text, int start, int end, Paint paint) | 绘制字符串 |
drawTextOnPath(参数太多,表格放不下了,需要自己查吧...) | 沿着路径绘制字符串 |
clipRect(float left, float top, float right, float bottom) | 剪切一个矩形区域 |
clipRegion(Region region) | 剪切制定区域 |
drawArc(参数太多,表格放不下了,需要自己查吧...) | 绘制弧 |
drawBitmap(Bitmap bitmap, rect src, Rect dst, Paint paint) | 绘制从源位图"挖取“的一块 |
drawBitmap(Bitmap bitmap, float left, float top, Paint paint) | 绘制位图 |
上表的各种方法具体使用不一一赘述了,大家看参数和简要说明应该就明白了,实在不懂可以搜索一下,具体用法我可能以后再开一篇总结吧~
我们上表就可以看出,Canvas就是提供给我们绘制基本图形的方法,复杂的图形也就是靠这些组合起来的~
此外,Canvas还提供了坐标变换的方法(Canvas的坐标原点在View的左上角,坐标轴方向遵循Android坐标系)
方法签名 | 简要说明 |
---|---|
rotate(float degrees, float px, float py) | 旋转变换 |
scale(float sx, float sy, float px, float py) | 缩放变换 |
skew(float sx, float sy) | 倾斜变换 |
translate(float dx, float dy) | 移动坐标原点 |
我们也可以看出,Canvas的使用,离不开 Paint,下面我们看一下Pait的方法
Paint
Paint 就好像生活中的画笔一样
setARGB(int a, int r, int g, int b) | 设置颜色 |
setAlpha(int a) | 设置透明度 |
setAntiAlias(boolean aa) | 设置是否抗锯齿 |
setColor(int color) | 设置颜色 |
setPathEffect(PathEffect effect) | 设置绘制路径时的路径效果 |
setShader(Shader shader) | 设置画笔的填充效果 |
setShadowLayer(float radius, float dx, float dy, int color) | 设置阴影 |
setStrokeWidth(float width) | 设置画笔的笔尖宽度 |
setStrokeJoin(Paint.Join join) | 设置画笔转弯处的连接风格 |
setStyle(Paint.Style style) | 设置Pain的填充风格 |
setTextAlign(Paint.Align align) | 设置绘制文本时的文字对齐方式 |
setTextSize(float textSize) | 设置绘制文本时的文字大小 |
根据方法的简要说明,很清晰明了吧~
具体使用,自己试一试就更清楚啦~
下面我们直接上手做一个复合图形吧
效果图如下
就是一个常见的钟表盘(你会发现这个钟表盘的数字有点问题,接下来我们来看看实现代码就明白这个问题是怎么回事啦)
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class Clock extends View{
public Clock(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Clock(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas){
// 画外圆
Paint paintCircle = new Paint();
paintCircle.setStyle(Paint.Style.STROKE);
//设置抗锯齿,防止缩放变换后图片出现锯齿边缘
paintCircle.setAntiAlias(true);
paintCircle.setStrokeWidth(5);
//getWidth() 和 getHeight()是View自己的方法 获取View的宽和高
//我们画一个以View中心点为圆心,半径为View宽度一半的圆
//从这个函数参数的传递,我们也可以看出,Canvas的坐标原点在View左上角
canvas.drawCircle(this.getWidth()/2, this.getHeight()/2, this.getWidth()/2, paintCircle);
//画刻度的画笔
Paint paintDegree = new Paint();
paintDegree.setTextAlign(Paint.Align.CENTER);
//从0点(12点)开始画刻度
for(int i = 0; i < 12; i ++){
//区分特殊点
if(i == 0 || i == 3 || i == 6 || i == 9){
//特殊的刻度,线更长,笔尖宽度更大
paintDegree.setStrokeWidth(5);
paintDegree.setTextSize(30);
//大家自己琢磨一下这个线的起点和终点吧
canvas.drawLine(this.getWidth()/2,
this.getHeight()/2 - this.getWidth()/2,
this.getWidth()/2,
this.getHeight()/2 - this.getWidth()/2 + 60,
paintDegree);
String clockTime = null;
//表盘上一般都把0点显示为12点
if(i == 0)
clockTime = String.valueOf(i + 12);
else
clockTime = String.valueOf(i);
canvas.drawText(clockTime, this.getWidth()/2, this.getHeight()/2 - this.getWidth()/2 + 90, paintDegree);
}else{
paintDegree.setStrokeWidth(3);
paintDegree.setTextSize(15);
canvas.drawLine(this.getWidth()/2,
this.getHeight()/2 - this.getWidth()/2,
this.getWidth()/2,
this.getHeight()/2 - this.getWidth()/2 + 30,
paintDegree);
String clockTime = String.valueOf(i);
canvas.drawText(clockTime, this.getWidth()/2, this.getHeight()/2 - this.getWidth()/2 + 60, paintDegree);
}
//这个地方,我们就用到了旋转坐标系的方法来达到每30度角画一个刻度
//注意,这个是旋转坐标系!并不是旋转画布,也就是说,单独执行这个方法,你看不到任何效果的!
canvas.rotate(30, this.getWidth()/2,this.getHeight()/2);
}
}
//这里我们没有重写onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
代码注释的比较清楚,唯一就是旋转坐标系这个我想还是再强调一下。
因为当时自己开始以为是旋转画布,调用后,并没有变化。
而就是因为坐标系旋转了,所以我们呢个6点,就倒过来了,像9一样。
最后在xml文件中引用这个布局就可以了
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.androidcanvas.MainActivity" >
<com.example.androidcanvas.Clock
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
项目源码AndroidOnDraw