【Android 系列-学习笔记】自定义 View 之绘图基础

323 阅读7分钟

1. Paint 使用基础

Paint 保存有关如何绘制几何图形,文本和位图的样式和颜色信息。

  • setAntiAlias(boolean):抗锯齿

    • 抗锯齿会根据特定的算法(插入特定像素,使不规则的图形边缘不会看起来有那么强的毛刺感),使绘制的不规则图形(如圆形、文字等)边缘看起来更平滑
    • 在绘制棱角分明的图像(如矩形、位图等)时,是不需要打开抗锯齿的.
  • setStyle(Paint.Style):设置填充方式,对于文字和几何图形都有效。style 的取值如下:

    • Paint.Style.STROKE:仅描边
    • Paint.Style.FILL:仅填充内部
    • Paint.Style.FILL_AND_STROKE:填充内部和描边
  • setShadowLayer(float radius, float dx, float dy, int shadowColor): 绘制内容时在内容底部加阴影

    • radius: 阴影的模糊范围
    • dx dy 阴影的偏移量
    • shadowColor 阴影颜色
  • setMaskFilter(MaskFilter maskfilter):绘制内容时在内容上蒙一层阴影

        BlurMaskFilter(float radius // 模糊半径, BlurMaskFilter.Blur Style, // 模糊类型
                        BlurMaskFilter.Blur.INNER, // 图像范围内加模糊
                        BlurMaskFilter.Blur.NORMAL, //图像范围内外都加模糊
                        BlurMaskFilter.Blur.OUTER, //图像留白,绘制外部模糊
                        BlurMaskFilter.Blur.SOLID//图像区域不受模糊影响,外部绘制模糊
                    )
    
  • setStrokeWidth(float width):设置描边宽度值,单位是 px。当画笔的 Style 样式是 STROKEFILL_AND_STROKE 时有效

  • setFilterBitmap(boolean:是否使用双线性过滤来绘制 Bitmap 图像在放大绘制的时候,默认使用的是最近邻插值过滤,这种算法简单,但会出现马赛克现象;而如果开启了双线性过滤,就可以让结果图像显得更加平滑。


2. Canvas 使用基础

2.1 画布背景设置

有 3 中方法可以实现画布背景设置,分别如下:

void drawColor(int color)
void drawARGB(int a, int r, int g, int b)
void drawRGB(int r, int g, int b);

其中,drawColor() 函数中的参数 color 的取值必须是 8 位的 0xAARRGGBB 样式颜色值。 drawARGB() 函数允许分别传入 A、R、G、B 分量,每个颜色值的取值范围为 0~255,内部会根据这些颜色分量来构造出对应的颜色值。 drawRGB() 函数只允许传入R、G、B分量,透明度 Alpha 的值取 255。

2.2 画直线

/**
 * @param startX: 起始点 X 坐标
 * @param startY: 起始点 Y 坐标
 * @param stopX: 终点 X 坐标
 * @param stopY: 终点 Y 坐标
 */
void drawLine(float startX, float startY, float stopX, float stopY, Paint paint)

示例如下:

Paint linePaint = new Paint();
linePaint.setColor(Color.GRAY);
linePaint.setStyle(Paint.Style.FILL_AND_STROKE);
linePaint.setStrokeWidth(50);
canvas.drawLine(100, 100, 300, 300, linePaint);

linePaint.setStyle(Paint.Style.FILL);
canvas.drawLine(100, 100, 300, 300, linePaint);

linePaint.setStyle(Paint.Style.STROKE);
canvas.drawLine(100, 100, 300, 300, linePaint);

运行上面的示例,我们可以知悉:直线的粗细与画笔 Style 是没有关系的,而与 StrokeWidth 有关系

2.3 画点

/**
 * @param x: 点的 X 坐标
 * @param y: 点的 Y 坐标
 */
void drawPoint(float x, float y, Paint paint)

点的大小只与 paint.setStrokeWidth(width) 有关,与 paint.setStyle() 无关。

2.4 画矩形

2.4.1 矩形工具类 RectF、Rect 概述

这两个类都是矩形工具类,根据 4 个点构造出一个矩形结构。这两个类中的方法、成员变量完全一样,唯一的不同是:RectF 是用来保存 float 类型数值的矩形结构,而 Rect 是用来保存 int 类型数值的矩形结构

下面来看看这两个类的构造函数:

// RectF 类
RectF()
RectF(float left, float top, float right, float bottom)
RectF(RectF r)
RectF(Rect r)

// Rect 类
Rect()
Rect(int left, int top, int right, int bottom)
Rect(Rect r)

// 构造一个矩形结构
// 方法一:直接构造
Rect rect = new Rect(10, 10 , 100, 100);

// 方法二:间接构造
Rect rect = new Rect();
rect.set(10, 10, 100, 100);

2.4.2 画矩形

// 传入矩形的四个点来绘制矩形
void drawRect(float left, float top, float right, float bottom, Paint paint)

// 传入 RectF 类来绘制矩形
void drawRect(RectF rect, Paint paint)

// 传入 Rect 类来绘制矩形
void drawRect(Rect r, Paint paint)

绘制矩形可以理解为传递左上角的点以及右下角的点的位置以供绘制。


3. Color

系统提供了一个专门用来解析颜色的类:Color。Color 是 Android 中与颜色处理有关的类。

3.1 常量颜色

这个类定义了很多常量颜色值,可以通过 Color.xxx 来直接使用这些颜色。比如说红色,可以直接使用 Color.RED

int BLACK
int BLUE
int GYAN
int RED
......

3.2 构造颜色

// 带有透明度的颜色
public static int argb(int alpha, int red, int green, int blue) {
  return (alpha << 24) | (red << 16) | (gree << 8) | blue;
}

// 不带透明度的颜色
public static int rgb(int red, int green, int blue);

举个例子来解释上面的源码,我们知道一个色彩值的取值范围是 0 ~ 255,所以每个色彩值对应的二进制有 8 位,那么 argb 总共就有32位,Alpha 占据的是 24~32 这 8 位。所以这里需要左移 24 位,其他的都类似的原理。比如说 alpha 取 255,其二进制是 1111 11111,左移 24 位后就是 11111111 00000000 00000000 00000000

3.3 提取颜色分量

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

4. 路径

/**
* Canvas 绘制路径的方法
*
* @param path 路径
* @param paint 参数
*/
void drawPath(Path path, Paint paint)

4.1 画直线

/**
* @param x1 直线起点的 x 坐标
* @param y1 直线起点的 y 坐标
*/
void moveTo(float x1, float y1)

/**
* @param x2 直线起点的 x 坐标
* @param y2 直线起点的 y 坐标
*/
void lineTo(float x2, float y2)

/**
* 将路径的首尾点连接起来,形成闭环
*/
void close()

4.2 画弧形路径

/**
* @param oval 生成椭圆的矩形
* @param startAngle 弧开始的角度,以 X 轴正方向为 0°
* @param sweepAngle 弧持续的角度
*/
void arcTo(RectF oval, float startAngle, float sweepAngle)

使用示例:

Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);

RectF rectF = new RectF(100, 100, 600, 600);
canvas.drawRect(rectF, paint);

paint.setColor(Color.BLUE);
Path path = new Path();
path.moveTo(350, 350);
path.arcTo(rectF, -90, 90);
path.close();
canvas.drawPath(path, paint);

上面这段代码的效果图如下图所示:

image.png

通过分析,我们可以知道,我们所截取的椭圆在矩形区域中的弧形片段。示例代码中的起点在矩形的中心点,所以截取的是一个圆的 1/4。如果点发生了变化,如下所示:

paint.setColor(Color.BLUE);
Path path = new Path();
path.moveTo(300, 350);
path.arcTo(rectF, -90, 90);
path.close();
canvas.drawPath(path, paint);

效果如下:

image.png

4.3 Region

Region 是一块任意形状的封闭图形。

4.3.1 构造 Region

构造 Region 有两种方式:直接构造和间接构造。先看看第一种,直接构造:

// 复制一个 Region 区域的范围
public Region(Region region)

// 创建一个矩形区域
public Region(Rect r)

// 创建一个矩形区域
public Region(int left, int top, int right, int bottom)

由于 Canvas 中没有用来画 Region 的方法,所以要想将 Region 画出来,可以按照下面这种方法:

private void drawRegion(Canvas canvas, Region rgn, Paint paint) {
  RegionIterator iter = new RegionIterator(rgn);
  Rect r = new Rect();

  while (iter.next(r)) {
    canvas.drawRect(r, paint);
  }
}

然后我们来说说间接构造的方法。**间接构造主要是通过 Region() 空构造函数与 set 系列函数相结合来实现:

/**
* 置空
* 将原来的一个区域变量变成空变量,在利用其他的 set 函数重新构造区域
*/
public void setEmpty()

/**
* 利用新的区域替换原来的区域
*/
public boolean set(Region region)

/**
* 利用矩形所代表的区域替换原来的区域
*/
public boolean set(Rect r)

/**
* 根据矩形的两个角点构造出矩形区域来替换原来的区域
*/
public boolean set(int left, int top, int right, int bottom)

/**
* 根据路径的区域与某区域的交集构造出新的区域
* 
* @param path: 用来构造区域的路径
* @param clip: 与前面的 path 所构成的路径取交集,并将该交集设置为最终的区域
*/
public boolean setPath(Path path, Region clip)

4.3.2 区域相交

// 与指定矩形取并集,即将 Rect 所指定的矩形加入当前区域中
boolean union(Rect r)

// 用当前的 Region 对象与指定的 Rect 兑现过 Regioin 对象执行相交操作,并将结果赋值给当前的 Region 对象。
// 如果计算成功,则返回 true,否则返回 false
boolean op(Rect r, Op op)
boolean op(int left, int top, int right, int bottom, Op op)
boolean op(Region region, Op op)

// 以下两个函数允许我们传入两个 Region 对象进行区域操作,并将操作结果赋给当前的 Region 对象。
// 操作成功,返回 true;否则返回 false
boolean op(Rect rect, Region region, Op op)
boolean op(Region region1, Region region2, Region.Op op)

使用示例一:

Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);

Region region = new Region(10, 10, 200, 100);
region.union(new Rect(10, 10, 50, 300));
drawRegion(canvas, region, paint);

效果图:

image.png

使用示例二:

@Override
protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);

  Rect rect = new Rect(100, 100, 400, 200);
  Rect rect1 = new Rect(200, 0, 300, 300);
  regionOpTest(canvas, rect, rect1, Region.Op.DIFFERENCE);

  rect = new Rect(500, 100, 800, 200);
  rect1 = new Rect(600, 0, 700, 300);
  regionOpTest(canvas, rect, rect1, Region.Op.INTERSECT);

  rect = new Rect(100, 500, 400, 600);
  rect1 = new Rect(200, 400, 300, 700);
  regionOpTest(canvas, rect, rect1, Region.Op.UNION);

  rect = new Rect(500, 500, 800, 600);
  rect1 = new Rect(600, 400, 700, 700);
  regionOpTest(canvas, rect, rect1, Region.Op.XOR);

  rect = new Rect(100, 900, 400, 1000);
  rect1 = new Rect(200, 800, 300, 1100);
  regionOpTest(canvas, rect, rect1, Region.Op.REVERSE_DIFFERENCE);

  rect = new Rect(500, 900, 800, 1000);
  rect1 = new Rect(600, 800, 700, 1100);
  regionOpTest(canvas, rect, rect1, Region.Op.REPLACE);
}

private void regionOpTest(Canvas canvas, Rect rect, Rect rect1, Region.Op op) {
  Paint paint = new Paint();
  paint.setColor(Color.RED);
  paint.setStyle(Paint.Style.STROKE);
  paint.setStrokeWidth(2);
  canvas.drawRect(rect, paint);
  canvas.drawRect(rect1, paint);

  drawFill(canvas, rect, rect1, op);
}

private void drawFill(Canvas canvas, Rect rect, Rect rect1, Region.Op op) {
  Region region = new Region(rect);
  Region region1 = new Region(rect1);
  region.op(region1, op);
  Paint paint_fill = new Paint();
  paint_fill.setColor(Color.GREEN);
  paint_fill.setStyle(Paint.Style.FILL);
  drawRegion(canvas, region, paint_fill);
}

private void drawRegion(Canvas canvas, Region rgn, Paint paint) {
  RegionIterator iter = new RegionIterator(rgn);
  Rect r = new Rect();

  while (iter.next(r)) {
    canvas.drawRect(r, paint);
  }
}

效果图:

image.png