Android 中 Canvas的功能
一. 如何在一屏幕上绘图
三个概念:
- 需要有画布Canvas
- 需要有笔Paint
- 需要有坐标系,画笔默认是在左上角(0,0)位置绘制的。我们可以通过移动画布坐标原点的形式,实现在不同位置绘制。
1. 移动坐标原点
简单绘制一个图
我们将画笔Canvas.translate(100,400)移动之后再进行绘制,这时画布的原点已经变化了。
2. 视图坐标系
理论上 Canvas 这张纸是没有边界的,但是我们的手机屏幕是有界的。我们可以理解为我们透过一个方形的洞(手机屏幕)看一张巨画(Canvas)。
而这里我们就又存在一个问题了,因为刚才的移动,我们是移动的原点,也就是说我们的画布是静止不动的,只是落笔点一直在变动,这就导致我们绘制的图对于用户来说是看不全的,所以我们需要进行移动 方形的洞 来查看这幅画。
举个例子,我们要查看最开始所说的画,可以通过移动 Screen框来查看这幅画,而这里又出现了一个坐标系,这一坐标系则为 视图坐标系,通过 scrollerTo
和 scrollerBy
进行移动该Screen框,正数则往正半轴,负数则往负半轴。
3. 小结
自定义控件中存在两个坐标系需要明确,用一句话总结如下:
- 绘图坐标系:决定我们的绘制的坐标
- 视图坐标系:决定我们所看到的画布范围
二、Canvas的剪刀手API
Canvas 中以 clip开头 的公有方法,用于裁剪画布的内容。裁剪之后画布之外将无法绘制, 我们抽取比较好玩的参数类型为Path的方法来分享,其余的都可以一一映射进来。
1、clipPath裁剪任意形状画布
public boolean clipPath(@NonNull Path path)
描述: 只留下 path内 的画布区域,而处于path范围之外的则不显示。
举个例子: 我们先准备好一个心形的路径Path,然后调用 clipPath
从画布中将此路径内的区域 “裁剪” 下来,最后为了我们观察,使用drawColor
“染”上酒红色。
// 第一步:创建 心形路径 mPath
....省略,具体请移步demo(我会贴在末尾的)
// 第二步:从画布 canvas 裁剪下心形路径之内的区域
canvas.clipPath(mPath);
// 第三步:涂酒红色
canvas.drawColor(mBgColor);
【这个有一个网上的】 自带美感的贝塞尔曲线原理与实战
画心型路径基本是使用贝塞尔曲线的( 路径关系可以参考这个画心),路径关系如文章所示本章仅讲解和绘制相关的知识。
效果如图所示:
代码如下:
/**
* 参考链接:https://blog.csdn.net/BigBoySunshine/article/details/53898806
*/
public class HartView extends View {
public HartView(Context context) {
super(context);
init();
}
public HartView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public HartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
}
private Paint mPaint = new Paint();
private Path mPath = new Path();
@Override
protected void onDraw(Canvas canvas) {
int mWidth = getWidth();
int mHeight = getHeight();
for (int i = 0 + 180; i < 361 + 180; i++) {
double sit = i * 2 * Math.PI / 360d;
double x = 20 * 16 * Math.pow(Math.sin(sit), 3);
double y = 20 * (13 * Math.cos(sit) - 5 * Math.cos(2 * sit) - 2 * Math.cos(3 * sit) - Math.cos(4 * sit));
if (i == 180) {
mPath.moveTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));scrcpy
}
mPath.lineTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
if (canvas != null)
canvas.drawPath(mPath, mPaint);
}
}
}
/**
* 参考链接:https://blog.csdn.net/BigBoySunshine/article/details/53898806
*/
public class OutsideHartView extends View {
public OutsideHartView(Context context) {
super(context);
init();
}
public OutsideHartView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public OutsideHartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
}
private Paint mPaint = new Paint();
private Path mPath = new Path();
@Override
protected void onDraw(Canvas canvas) {
int mWidth = getWidth();
int mHeight = getHeight();
for (int i = 0 + 180; i < 361 + 180; i++) {
double sit = i * 2 * Math.PI / 360d;
double x = 20 * 16 * Math.pow(Math.sin(sit), 3);
double y = 20 * (13 * Math.cos(sit) - 5 * Math.cos(2 * sit) - 2 * Math.cos(3 * sit) - Math.cos(4 * sit));
if (i == 180) {
mPath.moveTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
}
mPath.lineTo(mWidth / 2 + (float) (x), mHeight / 2 - (float) (y));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
canvas.clipOutPath(mPath);
}
canvas.drawColor(getContext().getResources().getColor(R.color.color_cd6155));
}
2. clipRect裁剪矩形
public