一、简介
历史就不多讲了,因为控制简便却具有极强的描述能力,贝塞尔曲线在工业设计领域迅速得到了广泛的应用。也就是使用Ps中钢笔的工具。
这个网站可以玩一玩,对前期的了解很好。
https://cubic-bezier.com/#.17,.67,.83,.67
二、原理
类型 | 作用 |
---|---|
数据点 | 确定曲线的起始和结束位置 |
控制点 | 确定曲线的弯曲程度 |
一阶贝塞尔曲线
一阶曲线是没有控制点的,仅有两个数据点(A 和 B),最终效果一个线段。直接是从P0到P1。
github
对应函数lineTo
。
二阶贝塞尔曲线
二阶则有了控制点,如下图:
- P0、P2分别连接控制点P1,生成两条线
- 在P0-P1和P1-P2分别取两点Q0和Q1,使得P0-Q0:P0-P1 = P1-Q1:P1-P2
- 又连接Q0和Q1,再在上面取点B,使得Q0-B:Q0-Q1 = P0-Q0:P0-P1
github
这样就完成了所有点、线的位置,最后我们在时间内做运动,就可以画出贝塞尔曲线了。
github
|
对应函数quadTo
。
三阶贝塞尔曲线
与二阶曲线同理方法,建立三阶曲线如下图。
github
githubpublic class BezierView2 extends View {
private Paint mPaint, mPointPaint, mLinePaint;
// 定点
private PointF one, two, three, four;
public BezierView2(Context context) {
super(context);
init();
}
void init() {
mPaint = new Paint();
mPaint.setColor(getResources().getColor(R.color.colorAccent));
mPaint.setStrokeWidth(8);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setTextSize(60);
mPointPaint = new Paint();
mPointPaint.setColor(getResources().getColor(R.color.colorPrimaryDark));
mPointPaint.setStrokeWidth(20);
mPointPaint.setTextSize(60);
mLinePaint = new Paint();
mLinePaint.setColor(getResources().getColor(R.color.colorPrimary));
mLinePaint.setStrokeWidth(3);
mLinePaint.setTextSize(60);
one = new PointF(100, 100);
two = new PointF(500, 100);
three = new PointF(500, 500);
four = new PointF(100, 500);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 画点
canvas.drawPoint(one.x, one.y, mPointPaint);
canvas.drawPoint(two.x, two.y, mPointPaint);
canvas.drawPoint(three.x, three.y, mPointPaint);
canvas.drawPoint(four.x, four.y, mPointPaint);
// 画线
canvas.drawLine(one.x, one.y, four.x, four.y, mLinePaint);
canvas.drawLine(four.x, four.y, three.x, three.y, mLinePaint);
canvas.drawLine(three.x, three.y, two.x, two.y, mLinePaint);
// 贝塞尔
Path path = new Path();
path.moveTo(one.x, one.y);
path.cubicTo(four.x, four.y, three.x, three.y, two.x, two.y);
canvas.drawPath(path, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
four.x = event.getX();
four.y = event.getY();
invalidate();
return true;
}
}
对应函数cubicTo
。
三、实例
QQ红点
原理如图:
github
- 图中可以看到两条贝塞尔曲线就是用两个数据点(红色)和一个控制点(蓝色)画出来的。
- 可以看到角度x都是相等的(可以自己推导)。
- 最后只要覆盖颜色,就可以了。
为了获取4个红色的数据点,我们需要做Sin、Cos的三角函数处理:// 获取两个圆的4点坐标
void getFourPoint() {
// 角度的sin和cos
float sin = Math.abs(mMainCircle.y - mClientCircle.y) / getClicleSpace();
float cos = Math.abs(mMainCircle.x - mClientCircle.x) / getClicleSpace();
// 获取静态圆水平和垂直长度
float x1 = sin * mMainRadius;
float y1 = cos * mMainRadius;
// 获取动态圆水平和垂直长度
float x2 = sin * mClientRadius;
float y2 = cos * mClientRadius;
// 判断斜度
if ((mMainCircle.x - mClientCircle.x)/(mMainCircle.y - mClientCircle.y) > 0) {
// 同号
// 获取静态圆两点
mMainLeftPoint.x = mMainCircle.x - x1;
mMainLeftPoint.y = mMainCircle.y + y1;
mMainRightPoint.x = mMainCircle.x + x1;
mMainRightPoint.y = mMainCircle.y - y1;
// 获取动态圆两点
mClientLeftPoint.x = mClientCircle.x - x2;
mClientLeftPoint.y = mClientCircle.y + y2;
mClientRightPoint.x = mClientCircle.x + x2;
mClientRightPoint.y = mClientCircle.y - y2;
} else {
//异号
// 获取静态圆两点
mMainLeftPoint.x = mMainCircle.x + x1;
mMainLeftPoint.y = mMainCircle.y + y1;
mMainRightPoint.x = mMainCircle.x - x1;
mMainRightPoint.y = mMainCircle.y - y1;
// 获取动态圆两点
mClientLeftPoint.x = mClientCircle.x + x2;
mClientLeftPoint.y = mClientCircle.y + y2;
mClientRightPoint.x = mClientCircle.x - x2;
mClientRightPoint.y = mClientCircle.y - y2;
}
}
// 获取两圆心距离
float getClicleSpace() {
mCircleSpace = (float)Math.hypot((double) Math.abs(mMainCircle.x - mClientCircle.x), (double)Math.abs(mMainCircle.y - mClientCircle.y));
return mCircleSpace;
}
- 首先我们需要知道X角度的Sin、Cos。
- 然后再分别获取4个位置点的偏移量
- 最后根据斜率来计算对应的4点坐标
然后在onDraw中绘制图片@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制固定圆
canvas.drawCircle(mMainCircle.x, mMainCircle.y, mMainRadius, mPaint);
// 绘制动态圆
canvas.drawCircle(mClientCircle.x, mClientCircle.y, mClientRadius, mPaint);
getPointF();
getFourPoint();
// 绘制贝塞尔
Path path = new Path();
path.moveTo(mMainLeftPoint.x, mMainLeftPoint.y);
path.quadTo(pointF.x, pointF.y, mClientLeftPoint.x, mClientLeftPoint.y);
path.lineTo(mClientRightPoint.x, mClientRightPoint.y);
path.quadTo(pointF.x, pointF.y, mMainRightPoint.x, mMainRightPoint.y);
path.close();
canvas.drawPath(path, mPaint);
}
根据代码可以知道,绘制固定的两个圆,和两条贝塞尔曲线即可,最后调用close
来将曲线闭合。
最后在滑动使只需要更新点位即可:@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mClientCircle.x = event.getX();
mClientCircle.y = event.getY();
invalidate();
break;
case MotionEvent.ACTION_UP:
if (getClicleSpace() > MAX_SPACE) {
// 断开并重新绘制心位置
mClientCircle.x = event.getX();
mClientCircle.y = event.getY();
mMainCircle.x = event.getX();
mMainCircle.y = event.getY();
invalidate();
} else if (getClicleSpace() <= MAX_SPACE) {
// 返回原处
mClientCircle.x = mMainCircle.x;
mClientCircle.y = mMainCircle.y;
invalidate();
} else {
// 继续显示
mClientCircle.x = event.getX();
mClientCircle.y = event.getY();
invalidate();
}
break;
case MotionEvent.ACTION_MOVE:
if (getClicleSpace() > MAX_SPACE) {
// 断开并重新绘制心位置
mClientCircle.x = event.getX();
mClientCircle.y = event.getY();
mMainCircle.x = event.getX();
mMainCircle.y = event.getY();
invalidate();
} else {
mClientCircle.x = event.getX();
mClientCircle.y = event.getY();
invalidate();
}
break;
}
return true;
}
画圆
可以看到这里使用的是三阶贝塞尔曲线。蓝点为数据点,红点为控制点。每个圆/4都需要两个控制点来生成,而这里的两个固定数值R和m都是经过推导出来的,我们只需要引用就好。
|