基本图形绘制

  • Paint 画笔,设置 画笔大小、粗细、画笔颜色、透明度、字体等样式
  • Canvas 画布,画出成品的东西,如圆形、矩形、文字等。

Paint

基础使用:

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View

class BasicView: View {
constructor(context: Context): super(context)

constructor(context: Context, atts: AttributeSet): super(context,atts)

constructor(context: Context, atts: AttributeSet, defStyle: Int): super(context,atts,defStyle)

@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
//设置画笔的基本属性
val paint = Paint()
paint.run {
//设置画笔颜色
color = Color.RED
//设置画笔填充样式
style = Paint.Style.STROKE
//设置画笔宽度
strokeWidth = 50f
}
//画圆 cx: 圆心x坐标,cy: 圆心y坐标
canvas?.drawCircle(190f,200f,150f,paint)
}
}

具体代码见Github

Paint paint = new Paint();
paint.setColor(Color.GREEN); //设置画笔颜色
paint.setStyle(Paint.Style.FILL); //设置画笔样式 FILL : 填充, STROKE : 描边, FILL_AND_STROKE : 填充和描边
paint.setStrokeWidth(5); //画笔宽度 px

下面绘制一大一小两个圆,并且将这两个圆叠加起来,上方的圆半透明,代码如下:

val paint = Paint()
paint.run {
//设置画笔颜色
color = Color.RED
//设置画笔填充样式
style = Paint.Style.FILL
//设置画笔宽度
strokeWidth = 50f
}
canvas?.drawCircle(190f,200f,150f,paint)
paint.color = 0x7EFFFF00
canvas?.drawCircle(190f,200f,100f,paint)

效果如下:

paint1-1

paint.style

完整的函数生命如下:

public void setStyle(Style style) { }

该函数用于设置填充样式,对于文字和集合图形都有效。style的取值如下:

  • Paint.Style.FILL 仅填充内部
  • Paint.Style.FILL_AND_STROKE 填充内部和描边
  • Paint.Style.STROKE 仅描边

下面以绘制圆形为例,看一下这三个不同的类型,效果图如下(代码见Github

Paint.Style

明显可见,FILL_AND_STROKEFILLSTROKE叠加在一起的效果,FILL_AND_STROKEFILL多了一个描边的宽度。

paint.strokeWidth

完整的函数声明如下:

public void setStrokeWidth(float width)

设置用于描边宽度,单位是px。当画笔的StyleFILLSTROKEFILL_AND_STROKE时有效。

此外,paint还有一些属性和方法

  • paint.isAntiAlias 标识是否开启抗锯齿功能。抗锯齿是依赖算法的,一般在绘制不规则的图形时使用,比如圆形、文字等。在绘制棱角分明的图像,比如一个矩形,一张位图,是不需要打开抗锯齿功能的。

  • paint.color 设置画笔的颜色

Canvas

设置画布背景

canvas.drawColor(Color.GREEN);  // drawColor(@ColorInt int color)
canvas.drawRGB(255,0,255); //drawRGB(int r, int g, int b)
canvas.drawARGB(0xFF,0xFF,0,0); //drawARGB(int a, int r, int g, int b)

其中drawColor()函数中参数color的取值必须是8位的0xAARRGGBB样式颜色值。

drawARGB()函数允许分别传入A、R、G、B分量,每个颜色值得取值范围都是0255(对应十六进制数0x000xFF),内部会通过这些颜色分量构造出对应的颜色值。

drawRGB()函数只允许传入R、G、B分量,透明度Alpha的取值为255

比如将画布默认颜色填充为蓝色。

canvas?.run {
drawColor(Color.BLUE)
//这两种写法也可以
// drawARGB(0xFF,0x00,0x00,0xFF)
// drawRGB(0x00,0x00,0xFF)
}

canvas

画直线

void drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint)

参数含义:

  • startX:起始点X坐标
  • startY:起始点Y坐标
  • stopX:终点X坐标
  • stopY:终点Y坐标

示例如下:

paint.run {
color = Color.RED
style = Paint.Style.FILL_AND_STROKE
strokeWidth = 50f
}
canvas?.run {
drawLine(100f,100f,200f,200f,paint)
}

当设置不同的Style类型时,效果如下图所示。

image-20230329171700360

可以看到,直线的粗细和笔画的Style是没有关系的。

当设置不同的strokeWidth时,效果如下:

canvas-strokeWidth

可见,直线的粗细与paint.strokeWidth有直接关系。所以,一般而言,paint.strokeWidthStyle起作用时,用于设置描边宽度;在Style不起作用时,用于设置笔画宽度。

画点

void drawPoint(float x, float y, @NonNull Paint paint)
  • float x:点的X坐标
  • float y:点的Y坐标

示例如下:

val paint = Paint()
paint.run {
color = Color.CYAN
strokeWidth = 15f
}
canvas?.drawPoint(100f,100f,paint)

代码很简单,就是在(100,100)的位置画了一个点。同样点的大小只与paint.strokeWidth有关,而与paint.style无关

矩形

工具类介绍

RectF和Rect区别:RectF里面参数是float类型,Rect里面参数是int类型`

public RectF() 
public RectF(float left, float top, float right, float bottom)
public RectF(@Nullable RectF r)
public RectF(@Nullable Rect r)
public Rect()
public Rect(int left, int top, int right, int bottom)
public Rect(@Nullable Rect r)

可以看到RectFRect的构造函数基本相同,不同的只是RectF所保存的数值类型是float类型,而Rect所保存的数值类型是int类型。

一般而言,要构造一个矩形结构,可以通过以下两种方式来实现。

//直接构造
val rect = Rect(10,10,100,100)
//间接构造
val rect1 = Rect()
rect1.set(10,10,100,100)

看完了矩形的存储结构RectFRect之后,再来看看矩形的绘制方法。

public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) 
public void drawRect(@NonNull RectF rect, @NonNull Paint paint)
public void drawRect(@NonNull Rect r, @NonNull Paint paint)

第一个函数是通过直接传入矩形的4个点来绘制的,第二、三个是通过根据传入的Rect或者RectF的矩形变量来指定所绘制的矩形的。

paint.run {
color = Color.CYAN
strokeWidth = 10f
style = Paint.Style.STROKE
}
//直接构造
canvas?.drawRect(10f,10f,100f,100f,paint)
//使用RectF构造
paint.style = Paint.Style.FILL
val rectF = RectF(210f,10f,300f,100f)
canvas?.drawRect(rectF,paint)

这里绘制了两个同样大小的矩形,第一个直接用4个点来绘制矩形,并且填充为描边类型;第二个通过RectF来绘制矩形,并且填充内容。

效果图如下:

rect

圆角矩形

/**
* rect: 要绘制的图形
* rx:生成圆角的椭圆X轴半径
* ry: 生成圆角的椭圆Y轴半径
*/
void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)
void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,@NonNull Paint paint)

shape标签也可以设置矩形的圆角,与shape不同的是,drawRoundRect()函数不能针对每个角设置对应的椭圆,而只能统一设置4个角对应的椭圆。

private val paint: Paint = Paint()

init {
paint.color = Color.GREEN
paint.strokeWidth = 10f
paint.style = Paint.Style.FILL
}
val rectF = RectF(100f,10f,300f,100f)
canvas?.drawRoundRect(rectF,20f,10f,paint)

效果如下:

drawRoundRect

圆形

public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) 

参数:

  • cx 圆心点的X轴半径
  • cy 圆心点的Y轴半径
  • radius 圆的半径
val paint: Paint = Paint()
paint.color = Color.GREEN
paint.strokeWidth = 10f
paint.style = Paint.Style.STROKE
canvas?.drawCircle(150f,150f,100f,paint)

效果如下:

drawCircle

椭圆

椭圆是根据矩形生成的,以矩形的长为椭圆的X轴,以矩形的宽为椭圆的Y轴。

public void drawOval(@NonNull RectF oval, @NonNull Paint paint) 
public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint)
val rect = RectF(300f,10f,600f,100f)
canvas?.drawOval(rect,paint)

效果如下:

oval

弧是椭圆的一部分,而椭圆是根据矩形来生成的,所以弧也是根据矩形来生成的。

public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
@NonNull Paint paint) {

Color

前面提到,除手动组合颜色的方法外,系统还提供了一个专门用来解析颜色的类:ColorColor是Android中与颜色处理有关的类。

常量颜色

首先,它定义了很多常亮的颜色值,可以直接使用

@ColorInt public static final int BLACK       = 0xFF000000;
@ColorInt public static final int DKGRAY = 0xFF444444;
@ColorInt public static final int GRAY = 0xFF888888;
@ColorInt public static final int LTGRAY = 0xFFCCCCCC;
@ColorInt public static final int WHITE = 0xFFFFFFFF;
@ColorInt public static final int RED = 0xFFFF0000;
@ColorInt public static final int GREEN = 0xFF00FF00;
@ColorInt public static final int BLUE = 0xFF0000FF;
@ColorInt public static final int YELLOW = 0xFFFFFF00;
@ColorInt public static final int CYAN = 0xFF00FFFF;
@ColorInt public static final int MAGENTA = 0xFFFF00FF;

可以通过Color.XXX来直接使用这些颜色。

构造颜色

  1. 带有透明度的颜色
public static int argb(int alpha,int red,int green, int blue)
public static int argb(float alpha,float red,float green, float blue)

这个函数允许我们传入A、R、G、B 4个颜色分量,然后合并一个颜色。其中,alpha、red、green、blue 4个色彩分量的取值范围都是0~255

  1. 不带透明度的颜色
public static int rgb(float red, float green, float blue)
public static int argb(int alpha,int red,int green,int blue)

类似上面的带透明度的颜色,只是不需要传入alpha

  1. 提取颜色分量

我们不仅能通过Color类来合并颜色分量,而且能从一个颜色中提取指定的颜色分量。

public static int alpha(int color) 
public static int red(int color)
public static int green(int color)
public static int blue(int color)

我们能通过上面的4个函数提取出颜色对应的A、R、G、B颜色分量。

比如:

val green = Color.green(0xFF000F00)

得到的结果green的值就是0x0F

前面的示例中创建Paint对象和其他对象都是在onDraw()函数中实现的,这个实际开发是不被允许的。因为当需要重绘时,就会调用onDraw()函数,所以onDraw()中的变量会被重复创建,可能引发GC问题。一般在自定义控件的构造函数中创建变量,即在初始化时一次性创建。

路径 (Path)

在Android中,Path类就代表路径。

在Canvas中绘制路径的方法

public void drawPath(@NonNull Path path, @NonNull Paint paint)

直线路径

画一条直线路径,一般设计下面三个函数。

public void moveTo(float x1, float y1)

(x1,y1)是直线的起始点,即将直线路径的绘制点定在(x1,y1)位置

public void lineTo(float x2, float y2) 

(x2,y2)是直线的终点,又是下一次绘制直线路径的起始点;lineTo()函数可以一直使用。

public void close()

如果连续画了几条直线,但是没有形成闭环,那么调用close()函数会将路径收尾连接起来,形成闭环。

示例: 画一个三角形