Android开发 - Canvas类与Paint画笔与Draw绘制方法详解与使用
Canvas类是什么
- Android中Canvas类常用于自定义View等操作中,Canvas则如同一张画布可以在上面绘制内容,然后这张画布也可以叠加其他的图层或者平移旋转等操作。Canvas对象的获取方式有两种:一种我们通过重写onDraw方法,View中重写
onDraw(Canvas canvas)
Canvas对象会被当做参数传递过来,我们操作这个Canvas,效果会直接反应在View中(我们也可以在ViewGroup中对Canvas做操作,然后将这个Canvas通过diapatchDraw(Canvas canvas)
的方式传递给子View,子View将会按照Canvas的规则去绘制);另一种就是new的方式去创建。Canvas类持有“draw”调用。要绘制内容需要4个基本组件:一个用于持有像素的Bitmap,一个用于承载绘制调用的Canvas(写入位图),一个绘制图元(例如矩形(Rect),路径(Path),文本(text),位图(Bitmap))和一个画笔(Paint)( 描述图纸的颜色和样式)
绘制需要什么
paint(画笔)
- 画笔提供了最基础的绘画功能,颜色、画笔大小、样式等。我们只需要简单的设计几个参数就可以使用:
setColor();
:设置画笔的颜色setAntiAlias();
:设置画笔的锯齿效果setARGB();
:设置画笔的A、R、G、B颜色值setAlpha();
:设置画笔的Alpha值setTextSize();
:设置字体的尺寸setStyle();
:设置画笔的风格(空心或实心)setStrokeWidth();
:设置空心边框的宽度(线宽)
canvas(画布)
canvas 常用的API
绘制画笔:drawPaint
-
drawPaint(Paint paint)
- 参数解析:
- paint:这个画笔绘制出来就是当前view的颜色,填充满整个画布
代码实例
private void drawPaint(Canvas canvas) { canvas.drawPaint(paint); }
- 参数解析:
绘画圆弧:drawArc
canvas:drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
- 参数解析:
- RectF:画布的大小
- startAngle: 开始角度,这个值只能决定图形的开始角度,不能改变图的开角角度。
- sweepAngle:扫描的角度,正常就是图形的角度
- useCenter:是否以中心旋转,如果是,画中的扇形中心在圆中心,否则就是一个普通的扇形
- paint:画笔
- 参数解析:
拼色扇形实例
-
像一些统计图其实是通过绘制圆弧完成的,但是绘制的时候需要注意图层问题:Canvas是一层一层绘制的,如果处理多图形拼图,最大层应该在最下方
代码实例
RectF rectF = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()); if (paint == null) { paint = new Paint(); } paint.setColor(Color.RED); paint.setStrokeWidth(2); paint.setStyle(Paint.Style.FILL); canvas.drawArc(rectF, 0, 360, true, paint); canvas.save(); paint.setColor(Color.YELLOW); canvas.drawArc(rectF, 0, 150, true, paint); paint.setColor(Color.BLUE); canvas.drawArc(rectF, 0, 90, true, paint); paint.setColor(Color.GRAY); canvas.drawArc(rectF, 0, 30, true, paint);
-
这里面有四种扇形,红色、黄色、绿色、灰色:
-
灰色开角:30
-
绿色开角:90
-
黄色开角:150
-
红色开角:360(圆形)
-
-
视觉上:
- 灰色:30°
- 绿色:60°
- 黄色:60°
- 红色:210°
- 灰色:30°
技巧
- 先算出各个角度,再计算出各个扇形的开角。注意先绘制大图形,再绘制小的,否则360°先绘制会导致图形的遮挡问题
-
绘制路径:drawPath
-
canvas.drawPath(Path path, Paint paint)
- 参数解析:
-
path:路径值,又因为path可以完成各种路径的构建
-
paint:画笔
-
- 参数解析:
-
正常path绘制很多东西,如果一个画面由三个点组成
代码实例
private void drawPath(Canvas canvas) { Path path = new Path(); path.moveTo(100, 100); path.lineTo(300, 300); path.lineTo(200,300); path.close(); canvas.drawPath(path, paint); }
组合绘图:drawPicture
canvas.drawPicture(Picture picture)
:绘制复杂的图形或者需要耗时较长,可以通过该方法- 参数解析:
- picture:图片或者海报
- 参数解析:
绘制色块:drawARGB
-
canvas.drawARGB(int a, int r, int g, int b)
:这个色块类似一个填充色,以ARGB颜色来完成。值可以参考色值:0-255,2^8-1- 参数解析:
- a:透明度
- r:红色
- g:绿色
- b:蓝色
- 参数解析:
绘制bitmap资源:drawBitmap
-
canvas.drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
- 参数解析:
- bitmap:bitmap资源
- left:从布局左边的坐标点(类似View的X轴)开始绘制
- top:从布局的顶部的坐标点(类似View的Y轴)开始绘制
- paint:画笔
代码实例
private void drawBitmap(Canvas canvas) { canvas.drawBitmap(getBitmap(),0,0,paint); canvas.save(); }
- 参数解析:
图片扭曲特效:drawBitmapMesh
-
canvas.drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, colors, int colorOffset, Paint paint)
-
参数解析:
- bitmap:指定需要扭曲的源位图。
- meshwidth:该参数控制在横向上把该源位图划分成多少格
- meshheight:该参数控制在纵向上把该源位图划分成多少格
- verts:该参数是一个长度为
(meshWidth + 1)*(meshHeight+1)* 2
的数组,它记录了扭曲后的位图各"顶点"位置。虽然它是个一维数组,实际上它记录的数据是形如(x0, y0),(x1, y1),(x2, y2)......(xN, yN)
格式的数据,这些数组元素控制对bitmap位图的扭曲效果 - vertOffset:vert的偏移,也就是控制verts数组中从第几个数组元素开始才对bitmap进行扭曲(忽略veroffset之前数据的扭曲效果)
-
drawBitmapMesh方法对源位图扭曲时最关键的参数是meshWidth、meshHeight和verts这三个参数对扭曲的控制,如下图:
-
从上图可以看出,当程序希望调用drawBitmapMesh方法对位图进行扭曲时,关键是计算verts数组的值(该数组的值记录了扭曲后的位图上各"顶点"的坐标)
代码实例
-
下面的实例是通过drawBitmapMesh方法来控制图片的扭曲
-
自定义MeshView组件
package com.example.matrixdemo.view; import com.example.matrixdemo.R; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; /*** * 通过Canvas的drawBitmapMesh方法控制图片的扭曲效果 * @author Administrator * */ public class MyMeshView extends View { /** 源图片 */ private Bitmap bitmap; /** 定义常量,常量指定该图片横向上被分为20格 */ private final int MESH_WIDTH = 20; /** 定义常量,常量指定该图片纵向上被分为20格 */ private final int MESH_HEIGHT = 20; /** 记录该图片上包含的441个顶点 */ private final int COUNT = (MESH_WIDTH + 1) * (MESH_HEIGHT + 1); /** 定义一个数组,保存Bitmap上的21*21个点的坐标 */ private float[] verts = new float[COUNT * 2]; /** 定义一个数组,保存Bitmap上的21*21个点经过扭曲后的坐标 */ // 对图片进行扭曲的关键就是修改该数组里元素的值 private float[] orig = new float[COUNT * 2]; public MyMeshView(Context context) { this(context, null); } public MyMeshView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyMeshView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setFocusable(true); // 加载Bitmap资源 bitmap = ((BitmapDrawable) getResources().getDrawable( R.drawable.bg02)).getBitmap(); // 获取图片的宽度,高度 float bitmapWidth = bitmap.getWidth(); float bitmapHeight = bitmap.getHeight(); int index = 0; for (int i = 0; i <= MESH_HEIGHT; i++) { float fy = bitmapHeight * i / MESH_HEIGHT; for (int j = 0; j < MESH_WIDTH; j++) { float fx = bitmapWidth * i / MESH_WIDTH; // 初始化orig,verts数组,初始化后,orig,verts两个数组均匀的保存了21*21个点的坐标 orig[index * 2 + 0] = verts[index * 2 + 0] = fx; //偶数 orig[index * 2 + 1] = verts[index * 2 + 1] = fy; //奇数 index += 1; } } // 设置背景色 setBackgroundColor(Color.WHITE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmapMesh(bitmap, MESH_WIDTH, MESH_HEIGHT, verts, 0, null, 0, null); } @Override public boolean onTouchEvent(MotionEvent event) { //调用warp方法根据触摸屏事件的坐标来扭曲verts数组 warp(event.getX(), event.getY()); return true; } /*** * 根据触摸事件的位置计算verts数组里个元素的值 * * @param x * X坐标 * @param y * Y坐标 */ private void warp(float cx, float cy) { for (int i = 0; i < MESH_WIDTH * 2; i += 2) { float dx = cx - orig[i + 0]; float dy = cy - orig[i + 1]; float dd = dx * dx + dy * dy; //计算每个坐标点与当前坐标点(cx,cy)之间的距离 float d = (float) Math.sqrt(dd); //计算扭曲度,距离当前点(cx,cy)越远,扭曲度越小 float pull = 80000 / (dd * d); //对verts数组(保存bitmap上21*21进过扭曲后的坐标)重新赋值 if(pull >= 1){ verts[i + 0] = cx; verts[i + 1] = cy; }else{ //控制各个顶点向触摸事件发生点进行偏移 verts[i + 0] = orig[i + 0] * dx * pull; verts[i + 1] = orig[i + 1] * dy * pull; } } //通知view组件重绘 invalidate(); } }
-
activity调用以及布局
-
Java调用
package com.example.matrixdemo; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class MeshActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mesh); } }
-
xml布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" >. <com.example.matrixdemo.view.MyMeshView android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
-
-
-
上面的程序中,warp方法会根据触摸点的位置动态修改verts数组里所有数组元素的值,这样就控制了drawBitmapMesh方法的扭曲效果
-
画圆:drawCircle
-
canvas.drawCircle(float cx, float cy, float radius, Paint paint)
- 参数解析
- cx:圆心x轴坐标
- cy:圆心y轴坐标
- radius:半径
- paint:画笔
代码实例
canvas.drawCircle(100, 100, 100, paint);
- 参数解析
划线:drawLine
-
canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
-
参数解析:
-
startX:起点x坐标
-
startY:起点y坐标
-
stopX:终点x坐标
-
stopY:终点y坐标
-
paint:画笔
-
代码实例
private void drawLine(Canvas canvas) { paint.setStrokeWidth(100); canvas.drawLine(0, 500, 500, 500, paint); }
- 由于线段是两点确定,所以(startX,startY)是起点,(stopX,stopY)是终点
- 如果想这条线很宽。通过设置画笔的宽度
paint.setStrokeWidth(100);
-
画椭圆:drawOval
-
canvas.drawOval(RectF oval, Paint paint)
- 参数解析:
- oval:设置一个矩形区域,扫描测量
- paint:画笔
- 参数解析:
-
canvas.drawOval(float left, float top, float right, float bottom, Paint paint)
-
参数解析:
-
left:左边坐标;在绘制中常表示为起点的Y轴坐标
-
top:上边左边;在绘制中常表示为起点的X轴坐标
-
right:右边坐标;在绘制中常表示为终点的Y轴坐标
-
bottom:下边坐标;在绘制中常表示为终点的Y轴坐标
-
paint:画笔
-
-
-
第二种方法的前4个参数实际上就是RectF类的参数,同样是创建一个矩形扫描测量区域进行放置一个椭圆
代码实例
private void drawOval(Canvas canvas) {
int width=getMeasuredWidth();
int height=getMeasuredHeight()/3;
canvas.drawOval(0,0,width,height,paint);
}
绘制补丁:drawPatch
-
canvas.drawPatch(NinePatch patch, Rect dst, Paint paint)
- 参数详解:
- patch:点九资源(.9.png),
- dst:绘制区域
- paint:画笔
- 参数详解:
-
此方法资料很少,估计是不太常用,简单了解一下即可:[点九资源](android draw9 patch介绍_android studio canvas drawpatch-CSDN博客)
代码实例
Bitmap bitmap = getBitmap(); NinePatch patch = new NinePatch(bitmap,bitmap.getNinePatchChunk()); canvas.drawPatch(patch, dst, paint)
绘制点:drawPoint
-
canvas.drawPoint(float x, float y, Paint paint)
- 参数解析:
- x:点的x轴坐标位置
- y:点的y轴坐标位置
- paint:画笔
- 参数解析:
-
点的大小取决于paint的画笔大小
代码实例
private void drawPoint(Canvas canvas) { paint.setStrokeWidth(20); canvas.drawPoint(100, 100, paint); }
绘制文字从点pos出发:drawPosText
-
canvas.drawPosText(String text, @Size(multiple = 2) float[] pos, Paint paint)
- 参数解析:
- text:文本
- pos:pos坐标(x,y)
- paint:画笔
代码实例
private void drawPosText(Canvas canvas) { float[] pos = new float[]{100, 100}; canvas.drawPosText("Hellow", pos, paint); }
- 参数解析:
绘制矩形区域:drawRect
-
canvas.drawRect(RectF rect, Paint paint)
- 参数解析:
- RectF:绘制区域
- paint:画笔
代码实例
private void drawRect(Canvas canvas) { RectF rectF=new RectF(0,0,500,500); canvas.drawRect(rectF,paint); }
- 参数解析:
绘制色域:drawRGB、drawColor
-
canvas.drawRGB(int r, int g, int b)
- 参数解析:
- r:rgb颜色值的r值
- g:rgb颜色值的g值
- b:rgb颜色值的b值
- 参数解析:
-
canvas.drawColor(int color)
- 参数解析:
- color:
- 参数解析:
-
以下只举例其一
代码实例
public void drawRGB(Canvas canvas) { canvas.drawRGB(200, 200, 200); }
在矩形内绘制圆角:drawRoundRect
-
canvas.drawRoundRect(RectF rect, float rx, float ry, Paint paint)
-
参数解析:
-
RectF :绘制区域
-
rx:圆角角度在矩形内的坐标x轴
-
ry:圆角角度在矩形内的坐标y轴
-
paint:画笔
-
注意事项
- 如果rx和ry为0,那么绘制出来的view就是矩形,如果rx,ry在矩形正中心,那么绘制出来的圆角就是圆形
- 如果x和y都小于矩形的宽和高,那么显示就是矩形四个角被裁剪
- 如果x和y都大于矩形的宽和高,那么显示就是圆
- 如果x和y其中一个小于宽和高,那么显示可能是一个不规则图形
代码实例
public void drawRoundRect(Canvas canvas) { RectF rectF=new RectF(0,0,200,200); canvas.drawRoundRect(rectF,10,10,paint); }
-
绘制字符:drawText
-
canvas.drawText(String text, float x, float y, Paint paint)
- 参数解析:
- text:文本
- x:位置的x轴坐标
- y:位置的y轴坐标
- paint:画笔
注意事项
- 绘制的时候paint style(画笔风格)必须设置成
paint.setStyle(Paint.Style.FILL);
否则会出现一团 - 文字的样式和大小,都是通过paint(画笔)来设置,关于paint可以自行查看
代码实例
private void drawText(Canvas canvas) { paint.setTextSize(100); canvas.drawText("你好",100,100,paint); }
- 参数解析:
绘制文本按路径显示:drawTextOnPath
-
canvas.drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
-
参数解析:
-
text :文本
-
path:路径,最终文本文字会按路径显示
-
hOffset :横向偏移,沿路径添加到文本起始位置的距离
-
vOffset :纵向偏移,定位文本的路径上方(-)或下方(+)的距离
-
paint:画笔
-
代码实例
private void drawTextOnPath(Canvas canvas) { paint.setTextSize(100); Path path=new Path(); path.moveTo(0,500); path.lineTo(500,500); path.lineTo(500,0); path.close(); canvas.drawTextOnPath("Hello word just do it",path,100,100,paint); }
-
绘制运行中的文本:drawTextRun
-
canvas.drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint paint)
-
参数解析:
-
text:文本
-
start:内容开始索引
-
end:内容结束的索引
-
contextStart:塑造语句的起点索引,默认从0开始
-
contextEnd:塑造语句的终结索引默认text的长度
-
x:位置x轴坐标
-
y:位置y轴坐标
-
isRtl:运行是否为镜像
-
paint:画笔
-
代码实例
private void drawTextRun(Canvas canvas) { paint.setTextSize(100); CharSequence text="Hello word just do it"; canvas.drawTextRun(text,0,text.length(),0,text.length(),100,100,true,paint); canvas.drawTextRun(text,0,text.length(),0,text.length(),100,600,false,paint); }
-
绘制多边形:drawVertices
canvas.drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, float[] texs, int texOffset, @Nullable int[] colors, int colorOffset, short[] indices, int indexOffset, int indexCount, Paint paint)
- 参数解析:
- mode:如何解释顶点数组
- vertexCount:顶点数组中的值数(如果非空,则为相应的texs和颜色数组)。每个逻辑顶点都是两个值(x,y),vertexCount必须是2的倍数
- verts:网格的顶点数组
- vertOffset:绘制前要跳过的顶点中的值数
- texs:可能为空。如果不为空,则指定要采样到当前着色器中的坐标(例如位图平铺或渐变)
- texOffset:绘制前要跳过的texs中的值数
- colors:可为空。如果不为空,则为每个顶点指定一种颜色,以便在三角形上进行插值。
- colorOffset:绘制前要跳过的颜色值的数目
- indices:如果不为null,则引用到顶点(texs,colors)数组的索引数组
- indexCount:如果不为空,索引数组中的条目数
- paint:画笔
- 由于资料很少,做一下简单的了解,在Android开发程序中应该是不常用的
- 参数解析: