Android 自定义View高级特效,神奇的贝塞尔曲线
效果图
效果图中我们实现了一个简单的随手指滑动的二阶贝塞尔曲线,还有一个复杂点的,穿越所有已知点的贝塞尔曲线。学会使用贝塞尔曲线后可以实现例如QQ红点滑动删除啦,360动态球啦,bulabulabula~
什么是贝塞尔曲线?
贝赛尔曲线(Bézier曲线)是电脑图形学中相当重要的参数曲线。更高维度的广泛化贝塞尔曲线就称作贝塞尔曲面,其中贝塞尔三角是一种特殊的实例。贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由Paul de Casteljau于1959年运用de Casteljau算法开发,以稳定数值的方法求出贝塞尔曲线。
读完上述贝塞尔曲线简介我还是一头雾水,来个示例呗。
示例
线性贝塞尔曲线
给定点P0、P1,线性贝塞尔曲线只是一条两点之间的直线。这条线由下式给出:
二次方贝塞尔曲线
二次方贝塞尔曲线的路径由给定点P0、P1、P2的函数B(t)追踪:
三次方贝塞尔曲线
P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝塞尔曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;公式如下:
N次方贝塞尔曲线
身为三维生物超出三维我很方,这里只给示例图。想具体了解的同学请左转度娘。
就当没看过上面
Android在API=1的时候就提供了贝塞尔曲线的画法,只是隐藏在Path#quadTo()和Path#cubicTo()方法中,一个是二阶贝塞尔曲线,一个是三阶贝塞尔曲线。当然,如果你想自己写个方法,依照上面贝塞尔的表达式也是可以的。不过一般没有必要,因为Android已经在native层为我们封装好了二阶和三阶的函数。
从一个二阶贝塞尔开始
自定义一个BezierView
初始化各个参数,花3s扫一下即可。
private Paint mPaint;
private Path mPath;
private Point startPoint;
private Point endPoint;
// 辅助点
private Point assistPoint;
public BezierView(Context context) {
this(context, null);
}
public BezierView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BezierView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mPaint = new Paint();
mPath = new Path();
startPoint = new Point(300, 600);
endPoint = new Point(900, 600);
assistPoint = new Point(600, 900);
// 抗锯齿
mPaint.setAntiAlias(true);
// 防抖动
mPaint.setDither(true);
}
在onDraw中画二阶贝塞尔
// 画笔颜色
mPaint.setColor(Color.BLACK);
// 笔宽
mPaint.setStrokeWidth(POINTWIDTH);
// 空心
mPaint.setStyle(Paint.Style.STROKE);
// 重置路径
mPath.reset();
// 起点
mPath.moveTo(startPoint.x, startPoint.y);
// 重要的就是这句
mPath.quadTo(assistPoint.x, assistPoint.y, endPoint.x, endPoint.y);
// 画路径
canvas.drawPath(mPath, mPaint);
// 画辅助点
canvas.drawPoint(assistPoint.x, assistPoint.y, mPaint);
上面注释很清晰就不赘述了。示例中贝塞尔是可以跟着手指的滑动而变化,我一拍榴莲,肯定是复写了onTouchEvent()!
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
assistPoint.x = (int) event.getX();
assistPoint.y = (int) event.getY();
Log.i(TAG, "assistPoint.x = " + assistPoint.x);
Log.i(TAG, "assistPoint.Y = " + assistPoint.y);
invalidate();
break;
}
return true;
}
最后将我们自定义的BezierView添加到布局文件中。至此一个简单的二阶贝塞尔曲线就完成了。假设一下,在向下拉动的过程中,在曲线上增加一个“小超人”,360动态清理是不是就出来了呢?有兴趣的可以自己拓展下。
以一个三阶贝塞尔结束
天气预报曲线图示例
(图一)
(图二)
概述
要想得到上图的效果,需要二阶贝塞尔和三阶贝塞尔配合。具体表现为,第一段和最后一段曲线为二阶贝塞尔,中间N段都为三阶贝塞尔曲线。
思路
先根据相邻点(P1,P2, P3)计算出相邻点的中点(P4, P5),然后再计算相邻中点的中点(P6)。然后将(P4,P6, P5)组成的线段平移到经过P2的直线(P8,P2,P7)上。接着根据(P4,P6,P5,P2)的坐标计算出(P7,P8)的坐标。最后根据P7,P8等控制点画出三阶贝塞尔曲线。
点和线的解释
黑色点:要经过的点,例如温度 蓝色点:两个黑色点构成线段的中点 黄色点:两个蓝色点构成线段的中点 灰色点:贝塞尔曲线的控制点 红色线:黑色点的折线图 黑色线:黑色点的贝塞尔曲线,也是我们最终想要的效果声明
为了方便讲解以及读者的理解。本篇以图一效果为例进行讲解。BezierView坐标都是根据屏幕动态生成的,想要图二的效果只需修改初始坐标,不用对代码做很大的修改即可实现。
那么,开始吧!
初始化参数
private static final String TAG = "BIZIER";
private static final int LINEWIDTH = 5;
private static final int POINTWIDTH = 10;
private Context mContext;
/** 即将要穿越的点集合 */
private List mPoints = new ArrayList<>();
/** 中点集合 */
private List mMidPoints = new ArrayList<>();
/** 中点的中点集合 */
private List mMidMidPoints = new ArrayList<>();
/** 移动后的点集合(控制点) */
private List mControlPoints = new ArrayList<>();
private int mScreenWidth;
private int mScreenHeight;
private void init(Context context) {
mPaint = new Paint();
mPath = new Path();
// 抗锯齿
mPaint.setAntiAlias(true);
// 防抖动
mPaint.setDither(true);
mContext = context;
getScreenParams();
initPoints();
initMidPoints(this.mPoints);
initMidMidPoints(this.mMidPoints);
initControlPoints(this.mPoints, this.mMidPoints , this.mMidMidPoints);
}
第一个函数获取屏幕宽高就不说了。紧接着初始化了初始点、中点、中点的中点、控制点。我们一个个的跟进。首先是初始点。
/** 添加即将要穿越的点 */
private void initPoints() {
int pointWidthSpace = mScreenWidth / 5;
int pointHeightSpace = 100;
for (int i = 0; i < 5; i++) {
Point point;
// 一高一低五个点
if (i%2 != 0) {
point = new Point((int) (pointWidthSpace*(i + 0.5)), mScreenHeight/2 - pointHeightSpace);
} else {
point = new Point