注明: 非常感谢 gcssloop 的博客,以下为我学习时的笔记记录。
更多Android学习笔记,请关注 Android-NoteBook,欢迎Star。
先来看一下常用的方法:
作用 | 相关方法 | 备注 |
---|---|---|
移动起点 | moveTo | 移动下一次操作的起点位置 |
设置终点 | setLastPoint | 重置当前path中最后一个点位置,如果在绘制之前调用,效果和moveTo相同 |
连接直线 | lineTo | 添加上一个点到当前点之间的直线到Path |
闭合路径 | close | 连接第一个点连接到最后一个点,形成一个闭合区域 |
添加内容 | addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo | 添加(矩形, 圆角矩形, 椭圆, 圆, 路径, 圆弧) 到当前Path (注意addArc和arcTo的区别) |
是否为空 | isEmpty | 判断Path是否为空 |
是否为矩形 | isRect | 判断path是否是一个矩形 |
替换路径 | set | 用新的路径替换到当前路径所有内容 |
偏移路径 | offset | 对当前路径之前的操作进行偏移(不会影响之后的操作) |
贝塞尔曲线 | quadTo, cubicTo | 分别为二次和三次贝塞尔曲线的方法 |
rXxx方法 | rMoveTo, rLineTo, rQuadTo, rCubicTo | 不带r的方法是基于原点的坐标系(偏移量), rXxx方法是基于当前点坐标系(偏移量) |
填充模式 | setFillType, getFillType, isInverseFillType, toggleInverseFillType | 设置,获取,判断和切换填充模式 |
提示方法 | incReserve | 提示Path还有多少个点等待加入**(这个方法貌似会让Path优化存储结构)** |
布尔操作(API19) | op | 对两个Path进行布尔运算(即取交集、并集等操作) |
计算边界 | computeBounds | 计算Path的边界 |
重置路径 | reset, rewind | 清除Path中的内容 reset不保留内部数据结构,但会保留FillType.rewind会保留内部的数据结构,但不保留FillType |
矩阵操作 | transform | 矩阵变换 |
在我们以前的学习中,大多都是一些直线,今天我们来看看其中的曲线部分,说到曲线,就不得不说 贝塞尔曲线。
贝塞尔曲线能干什么?
贝塞尔曲线的运用是十分广泛的,可以说贝塞尔曲线奠定了计算机绘图的基础(因为它可以将任何复杂的图形用精确的数学语言进行描述),在你不经意间就已经使用过它了。
你会使用Photoshop的话,你可能会注意到里面有一个钢笔工具,这个钢笔工具核心就是贝塞尔曲线。
你说你不会PS? 没关系,你如果看过前面的文章或者用过2D绘图,肯定绘制过圆,圆弧,圆角矩形等这些东西。这里面的圆弧部分全部都是贝塞尔曲线的运用。
贝塞尔曲线作用十分广泛,简单举几个的栗子:
我们先了解贝塞尔曲线是如何生成。
贝塞尔曲线使用一系列点来控制曲线状态。这些点简单分为两类:
类型 | 作用 |
---|---|
数据点 | 确定曲线的起始和结束位置 |
控制点 | 确定曲线的弯曲程度 |
一阶曲线原理:
一阶曲线是没有控制点的,仅有两个数据点(A和B),最终效果一个线段。
上图表示的是一阶曲线生成过程中的某一个阶段,动态过程可以参照下图。
注意:一阶曲线其实就是 lineTo
二阶曲线原理:
二阶曲线由两个数据点(A和C),一个控制点(B)来描述曲线状态,大致如下:
上图中红色曲线部分就是传说中的二阶贝塞尔曲线,那么红色曲线是如何生成的呢?我们以其中一个状态分析:
连接AB BC,并在AB上取点D,BC上取点E,使其满足条件:
连接DE,取点F,使得:
这样获取到的点F就是贝塞尔曲线上的一个点,动态过程如下:
三阶曲线原理:
三阶曲线由两个数据点(A 和 D),两个控制点(B 和 C)来描述曲线状态,如下:
三阶曲线计算过程与二阶类似,具体可以见下图动态效果:
三阶曲线对应的方法是 cubicTo
一阶曲线:
一阶曲线是一条线段,比较简单,可以参考Path。
二阶曲线:
我们知道二阶曲线是由两个数据点,一个控制点构成,下面上Demo
首先,两个数据点是控制贝塞尔曲线开始和结束的位置,比较容易理解,而控制点则是控制贝塞尔的弯曲状态。我们现在重点理解贝塞尔曲线弯曲状态与控制点的关系。
贝塞尔曲线在动态变化过程中有类似与橡皮筋的弹性效果。因为非常适用于一些弹性效果的实例。
public class CanvasView extends View {
private Paint paint;
private int mWidth,mheight;
//开始点,结束点,实时位置
private PointF start,end,control;
public CanvasView(Context context) {
super(context);
}
public CanvasView(Context context,AttributeSet attrs) {
super(context, attrs);
paint=new Paint();
paint.setColor(Color.BLACK);
paint.setStrokeWidth(8);
paint.setStyle(Paint.Style.STROKE);
paint.setTextSize(60);
start=new PointF(0,0);
end=new PointF(0,0);
control=new PointF(0,0);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth=w/2;
mheight=h/2;
//初始化数据点和控制点的位置
start.x=mWidth-200; //起始x
start.y=mheight; //起始y
end.x=mWidth+200; //结束x
end.y=mheight; //结束y
control.x=mWidth; //默认实时x为view一半
control.y=mheight-100; //默认实时y为view一半-100
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制数据点和控制点
paint.setColor(Color.GRAY);
paint.setStrokeWidth(20);
canvas.drawPoint(start.x,start.y,paint);
canvas.drawPoint(end.x,end.y,paint);
canvas.drawPoint(control.x,control.y,paint);
Log.e("Demo","x:"+control.x+"---y"+control.y);
//绘制辅助线
paint.setStrokeWidth(4);
canvas.drawLine(start.x,start.y,control.x,control.y,paint);
canvas.drawLine(end.x,end.y,control.x,control.y,paint);
//绘制贝塞尔曲线
paint.setColor(Color.RED);
paint.setStrokeWidth(8);
Path path=new Path();
/*
简单来说,就是起点,和终点已经确定,只需要更改相应的变化值,通过两个直线,即起点与终点沿曲线的直线,
就可以动态生成贝塞尔曲线
* */
//设置下一个点的起始位置
path.moveTo(start.x,start.y);
//二阶贝塞尔曲线
path.quadTo(control.x,control.y,end.x,end.y);
canvas.drawPath(path,paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//根据触摸位置更新控制点,并提示重绘
control.x=event.getX();
control.y=event.getY();
//刷新位置
invalidate();
return true;
}public class CanvasView extends View {
private Paint paint;
private int mWidth,mheight;
//开始点,结束点,实时位置
private PointF start,end,control;
public CanvasView(Context context) {
super(context);
}
public CanvasView(Context context,AttributeSet attrs) {
super(context, attrs);
paint=new Paint();
paint.setColor(Color.BLACK);
paint.setStrokeWidth(8);
paint.setStyle(Paint.Style.STROKE);
paint.setTextSize(60);
start=new PointF(0,0);
end=new PointF(0,0);
control=new PointF(0,0);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth=w/2;
mheight=h/2;
//初始化数据点和控制点的位置
start.x=mWidth-200; //起始x
start.y=mheight; //起始y
end.x=mWidth+200; //结束x
end.y=mheight; //结束y
control.x=mWidth; //默认实时x为view一半
control.y=mheight-100; //默认实时y为view一半-100
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制数据点和控制点
paint.setColor(Color.GRAY);
paint.setStrokeWidth(20);
canvas.drawPoint(start.x,start.y,paint);
canvas.drawPoint(end.x,end.y,paint);
canvas.drawPoint(control.x,control.y,paint);
Log.e("Demo","x:"+control.x+"---y"+control.y);
//绘制辅助线
paint.setStrokeWidth(4);
canvas.drawLine(start.x,start.y,control.x,control.y,paint);
canvas.drawLine(end.x,end.y,control.x,control.y,paint);
//绘制贝塞尔曲线
paint.setColor(Color.RED);
paint.setStrokeWidth(8);
Path path=new Path();
//设置下一个点的起始位置
path.moveTo(start.x,start.y);
//二阶贝塞尔曲线
path.quadTo(control.x,control.y,end.x,end.y);
canvas.drawPath(path,paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//根据触摸位置更新控制点,并提示重绘
control.x=event.getX();
control.y=event.getY();
//刷新位置
invalidate();
return true;
}
}
效果如图上所示
三阶曲线
三解曲线由两个数据点和两个控制点来控制曲线状态
public class CanvasView extends View {
private Paint mPaint;
//屏幕位置
private int centerX,centerY;
//开始点,结束点,辅助线两条
private PointF start,end,control1,control2;
private boolean mode=true;
public CanvasView(Context context) {
super(context);
//通过代码添加 addView,所以初始化放在单构造函数里
mPaint=new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(8);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setTextSize(60);
start=new PointF(0,0);
end=new PointF(0,0);
control1=new PointF(0,0);
control2=new PointF(0,0);
}
public CanvasView(Context context,AttributeSet attrs) {
super(context, attrs);
}
public void setMode(boolean mode){
this.mode=mode;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX=w/2;
centerY=h/2;
//初始化数据点和控制点的位置
start.x = centerX - 200;
start.y = centerY;
end.x = centerX + 200;
end.y = centerY;
control1.x = centerX;
control1.y = centerY - 100;
control2.x = centerX;
control2.y = centerY - 100;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 根据触摸位置更新控制点,并提示重绘
if (mode) {
control1.x = event.getX();
control1.y = event.getY();
} else {
control2.x = event.getX();
control2.y = event.getY();
}
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//drawCoordinateSystem(canvas);
// 绘制数据点和控制点
mPaint.setColor(Color.GRAY);
mPaint.setStrokeWidth(20);
canvas.drawPoint(start.x, start.y, mPaint);
canvas.drawPoint(end.x, end.y, mPaint);
//绘制手指按下的点 ->同一时刻变动一个
canvas.drawPoint(control1.x, control1.y, mPaint);
canvas.drawPoint(control2.x, control2.y, mPaint);
// 绘制辅助线
mPaint.setStrokeWidth(4);
//绘制控制线->开始线之间的线段
canvas.drawLine(start.x, start.y, control1.x, control1.y, mPaint);
//绘制控制点到未控制点之间的线段
canvas.drawLine(control1.x, control1.y,control2.x, control2.y, mPaint);
//绘制另一条线 ->未被控制的线到终点线的线段
canvas.drawLine(control2.x, control2.y,end.x, end.y, mPaint);
// 绘制贝塞尔曲线
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(8);
Path path = new Path();
//设置起点
path.moveTo(start.x, start.y);
//三阶贝塞尔曲线->左控制点,右控制点,结束点
path.cubicTo(control1.x, control1.y, control2.x,control2.y, end.x, end.y);
//绘制
canvas.drawPath(path, mPaint);
}
通过上面的注释,就会发现其实很简单,简单的原因就是已经为我们封装了相应的方法。但是我们必须明白贝塞尔曲线的生成规则。这样才能更好的去用。
三阶曲线相比于二阶曲线可以制作更加复杂的形状,但是对于高阶的曲线,用低阶的曲线组合也可达到相同的效果,就是传说中的降阶。因此我们对贝塞尔曲线的封装方法一般最高只到三阶曲线。
降阶与升阶
类型 | 释义 | 变化 |
---|---|---|
降阶 | 在保持曲线形状与方向不变的情况下,减少控制点数量,即降低曲线阶数 | 方法变得简单,数据点变多,控制点可能减少,灵活性变弱 |
升阶 | 在保持曲线形状与方向不变的情况下,增加控制点数量,即升高曲线阶数 | 方法更加复杂,数据点不变,控制点增加,灵活性变强 |
序号 | 内容 | 用例 |
---|---|---|
1 | 事先不知道曲线状态,需要实时计算时 | 天气预报气温变化的平滑折线图 |
2 | 显示状态会根据用户操作改变时 | QQ小红点,仿真翻书效果 |
3 | 一些比较复杂的运动状态(配合PathMeasure使用) | 复杂运动状态的动画效果 |
贝塞尔曲线的主要优点是可以实时控制曲线状态,并可以通过改变控制点实时让曲线进行平滑的状态变化。