Android高级UI --- canvas

前言

我们先来聊聊,在我们生活中如何绘制一张如下的图。

我们需要两样东西来绘制:

  1. 一张纸(Android 中的 canvas):用来承载我们绘制的内容。
  2. 一支笔(Android 中的 paint):负责绘制内容的轨迹。

有了这两样,我们就能在现实的场景中开始绘制了。

1、绘图坐标系

在 Android 的体系中,我们所谓的 "笔Paint" 和 "纸Canvas" 都是由App持有的,所以我们在绘制时就出现一个问题:我们怎么"告诉"App,确定我们想要绘制图形的落笔点?当然需要一个坐标系来进行交流。

这个 坐标系 便是我们经常所说的 绘图坐标系 。初始状态下,Canvas的左上角为原点,此时我们想画图中的红点,就非常的容易,只需要"告诉" App 在坐标(200,500)处画一个红点,这就达到了画图的效果了。所以我们可以明确的一点是 我们所有的画图坐标都是根据原点进行确定。

所以我们可以移动原点,达到整体坐标点的移动,例如还是画刚才的红点,我们可以先将原点水平移动100,垂直移动400。然后在进行绘制,这时红点的坐标就变为(100,100),具体如下图所示。

经过上面的简单讲述,我们可以知道,绘图过程中,我们的绘图坐标永远是跟随当前的原点,而画布的原点可以进行移动。

2、视图坐标系

理论上 Canvas 这张纸是没有边界的,但是我们的手机屏幕是有界的。我们可以理解为我们透过一个方形的洞(手机屏幕)看一张巨画(Canvas)。

又存在一个问题了,因为刚才的移动,我们是移动的原点,也就是说我们的画布是静止不动的,只是落笔点一直在变动,这就导致我们绘制的图对于用户来说是看不全的,所以我们需要进行移动 方形的洞 来查看这幅画。

可以通过移动 Screen框来查看这幅画,而这里又出现了一个坐标系,这一坐标系则为 视图坐标系 ,通过 scrollerToscrollerBy 进行移动该Screen框,正数则往正半轴,负数则往负半轴。

3、小结

自定义控件中存在两个坐标系需要明确,用一句话总结如下:

  1. 绘图坐标系:决定我们的绘制的坐标
  2. 视图坐标系:决定我们所看到的画布范围

=========================================================================

介绍Paint

绘制的基本形状由Canvas确定 ,但绘制出来的颜色,具体效果则由Paint确定

例子:

mPaint.setStyle(Paint.Style.FILL);  //设置画笔模式为填充

画笔有三种模式,如下:

STROKE                //描边
FILL                  //填充
FILL_AND_STROKE       //描边加填充

区分三者效果我们做如下实验:

java 复制代码
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(40);     //为了实验效果明显,特地设置描边宽度非常大

// 描边
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(200,200,100,paint);

// 填充
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(200,500,100,paint);

// 描边加填充
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(200, 800, 100, paint);

效果:

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比较难以理解和使用,故封装了一些常用的方法。

canvas绘制基本图形

绘制颜色:

绘制颜色是填充整个画布,常用于绘制底色。

canvas.drawColor(Color.BLUE); //绘制蓝色

创建画笔:

要想绘制内容,首先需要先创建一个画笔,如下:

java 复制代码
// 1.创建一个画笔
private Paint mPaint = new Paint();

// 2.初始化画笔
private void initPaint() {
    mPaint.setColor(Color.BLACK);       //设置画笔颜色
    mPaint.setStyle(Paint.Style.FILL);  //设置画笔模式为填充
    mPaint.setStrokeWidth(10f);         //设置画笔宽度为10px
}

// 3.在构造函数中初始化
public SloopView(Context context, AttributeSet attrs) {
   super(context, attrs);
   initPaint();
}

在创建完画笔之后,就可以在Canvas中绘制各种内容了。

绘制点:

可以绘制一个点,也可以绘制一组点,如下:

java 复制代码
canvas.drawPoint(200, 200, mPaint);     //在坐标(200,200)位置绘制一个点
canvas.drawPoints(new float[]{          //绘制一组点,坐标位置由float数组指定
      500,500,
      500,600,
      500,700
},mPaint);

坐标原点默认在左上角,水平向右为x轴增大方向,竖直向下为y轴增大方向。

绘制直线:

绘制直线需要两个点,初始点和结束点,同样绘制直线也可以绘制一条或者绘制一组:

java 复制代码
// 在坐标(300,300)(500,600)之间绘制一条直线
canvas.drawLine(300,300,500,600,mPaint);  
// 绘制一组线 每四数字(两个点的坐标)确定一条线 
canvas.drawLines(new float[]{             
    100,200,200,200,
    100,300,200,300
},mPaint);

绘制矩形:

我们都知道,确定一个矩形最少需要四个数据,就是对角线的两个点 的坐标值,这里一般采用左上角和右下角的两个点的坐标。

关于绘制矩形,Canvas提供了三种重载方法,第一种就是提供四个数值(矩形左上角和右下角两个点的坐标)来确定一个矩形 进行绘制。 其余两种是先将矩形封装为Rect或RectF(实际上仍然是用两个坐标点来确定的矩形),然后传递给Canvas绘制,如下:

java 复制代码
// 第一种
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);

为什么会有Rect和RectF两种?两者有什么区别吗?

两者最大的区别就是精度不同,Rect是int(整形)的,而RectF是float(单精度浮点型)的。除了精度不同,两种提供的方法也稍微存在差别。

绘制圆角矩形:

绘制圆角矩形也提供了两种重载方式,如下:

java 复制代码
// 第一种
RectF rectF = new RectF(100,100,800,400);
canvas.drawRoundRect(rectF,30,30,mPaint);

// 第二种
canvas.drawRoundRect(100,100,800,400,30,30,mPaint);

上面两种方法绘制效果也是一样的,但鉴于第二种方法在API21的时候才添加上,所以我们一般使用的都是第一种。

绘制椭圆:

相对于绘制圆角矩形,绘制椭圆就简单的多了,因为他只需要一个矩形作为参数:

java 复制代码
// 第一种
RectF rectF = new RectF(100,100,800,400);
canvas.drawOval(rectF,mPaint);

// 第二种
canvas.drawOval(100,100,800,400,mPaint);

同样,以上两种方法效果完全一样,但一般使用第一种。

绘制圆:

绘制圆形也比较简单, 如下:

canvas.drawCircle(500,500,400,mPaint);  // 绘制一个圆心坐标在(500,500),半径为400 的圆。

绘制圆形有四个参数,前两个是圆心坐标,第三个是半径,最后一个是画笔。

绘制圆弧:

先看方法:

java 复制代码
// 第一种
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}
    
// 第二种
public void drawArc(float left, float top, float right, float bottom, float startAngle,
            float sweepAngle, boolean useCenter, @NonNull Paint paint) {}

从上面可以看出,相比于绘制椭圆,绘制圆弧还多了三个参数:

startAngle  // 开始角度
sweepAngle  // 扫过角度
useCenter   // 是否使用中心

来前两个参数(startAngle, sweepAngel)的作用,就是确定角度的起始位置和扫过角度。

useCenter 使用了中心点之后绘制出来类似于一个扇形,而不使用中心点则是圆弧起始点和结束点之间的连线加上圆弧围成的图形。

绘制不规则多边形

使用Path类来绘制不规则多边形。

例子:

java 复制代码
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
 
public class CustomPolygonView extends View {
 
    private Path polygonPath = new Path();
    private Paint polygonPaint = new Paint();
 
    public CustomPolygonView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
 
    private void init() {
        polygonPaint.setAntiAlias(true);
        polygonPaint.setColor(0xFF0000FF); // 蓝色填充
        polygonPaint.setStyle(Paint.Style.FILL); // 设置填充样式
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
 
        // 创建不规则多边形路径
        polygonPath.reset();
        polygonPath.moveTo(100, 50); // 第一个顶点
        polygonPath.lineTo(200, 50); // 第二个顶点
        polygonPath.lineTo(150, 100); // 第三个顶点
        polygonPath.lineTo(100, 150); // 第四个顶点
        polygonPath.close(); // 首尾连接关闭路径
 
        // 绘制多边形
        canvas.drawPath(polygonPath, polygonPaint);
    }
}

我们创建了一个自定义View,在其onDraw方法中使用Path绘制了一个有四个顶点的不规则多边形。您可以通过调整polygonPath的顶点来创建不同形状的多边形。

绘制一串点路径虚线

要使用Canvas绘制一串点路径上的虚线,可以使用PaintsetPathEffect方法,并提供一个DashPathEffect对象。

例子:

java 复制代码
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.view.View;
 
public class DashedLineView extends View {
 
    private Paint paint;
    private Path path;
 
    public DashedLineView(Context context) {
        super(context);
        init();
    }
 
    private void init() {
        paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(5f);
        paint.setStyle(Paint.Style.STROKE);
        // 设置虚线效果,参数分别为:"点的间隔","点的长度","总的长度"
        paint.setPathEffect(new DashPathEffect(new float[]{10f, 5f}, 0f));
 
        path = new Path();
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 定义路径,这里以一个简单的直线为例
        path.reset();
        path.moveTo(10, 10);
        path.lineTo(300, 10);
 
        // 绘制虚线
        canvas.drawPath(path, paint);
    }
}

DashPathEffect被设置为每10个点一个间隔,每个点5个单位长度,总长度为50个单位。这将导致虚线由5个黑点和5个空白点组成的序列。你可以根据需要调整DashPathEffect构造函数的参数来改变虚线的样式。

画布的操作:

相关操作 简要介绍
save 保存当前画布状态
restore 回滚到上一次保存的状态
translate 相对于当前位置位移
rotate 旋转

tips:

自定义View流程梳理一遍(确定各个步骤应该做的事情):

步骤 关键字 作用
1 构造函数 初始化(初始化画笔Paint)
2 onMeasure 测量View的大小(暂时不用关心)
3 onSizeChanged 确定View大小(记录当前View的宽高)
4 onLayout 确定子View布局(无子View,不关心)
5 onDraw 实际绘制内容(绘制饼状图)
6 提供接口 提供接口(提供设置数据的接口)
相关推荐
Yongqiang Cheng3 分钟前
在线查看 Android 系统源代码 Android Code Search
android·在线查看·android 系统源代码·code search
CYRUS STUDIO7 分钟前
LineageOS源码下载和编译(Xiaomi Mi 6X,wayne)
android·刷机·lineageos·android源码编译
竹等寒1 小时前
中间件常见漏洞
android·web安全·网络安全·中间件
zeruns8023 小时前
如何用安卓玩Java版Minecraft,安卓手机安装我的世界Java版游戏的教程
android·java·智能手机·minecraft·mc
我命由我123455 小时前
ADB 之 logcat 极简小抄(过滤日志、保存日志到文件)
android·运维·adb·android studio·安卓·运维开发·android-studio
不吃饭的猪5 小时前
mysql一主2从部署
android·mysql·adb
振华OPPO6 小时前
我的5周年创作纪念日,不忘初心,方得始终。
android·java·android studio·安卓·安卓app
周杰伦的稻香13 小时前
MySQL5.7-虚拟列
android
ehviwer2314 小时前
MathType7.9绿色和谐版激活补丁包下载
android·macos·ios·cocoa·媒体
无问社区15 小时前
tp6.0.8反序列化漏洞的一些看法
android