分析并实现1到n阶贝塞尔
原文地址:http://blog.csdn.net/u014239140/article/details/76018422
什么事贝塞尔:
贝塞尔曲线又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。主要结构:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。
由于android系统给我们提供了前三阶的实现,其实在开发中用到三阶贝塞尔的情况都很少了,一般动画在二阶范围内。超过三阶的动画其实开销是很大的,需要不断的计算各个点。
首先看下公式以及效果图:
一阶:
二阶:
三节:
当然还有四阶曲线、五阶曲线......只不过随着控制点的增加,复杂维度会越来越高,如果按照公式来算难度也越来越大!
从图上看可以得出一个结论 : n阶贝塞尔的控制点数 = n阶-1。虽然从公式上理解是非常难得,我们在开发中,也不是必须要完全理解这些公式,大概知道原理即可。
既然系统给我们提供了前三阶的实现,就先来了解下api的使用吧!
前三阶贝塞尔基本操作:
假设我们要利用贝塞尔来实现下面标注的5条线,该怎么弄呢? 首先分析下,第1条 就是一条线段。简单的lineTo()就可以了,2和3其实都是二阶贝塞尔曲线,4和5都是三阶贝塞尔曲线,这里我为什么都画了两条呢?原因是这里涉及到了两个api。一个是基于左上角(0.0)计算的,一个是基于上一个点进行计算的(即相对计算)。
一阶api: lineTo()方法实现。
二阶api: ①quadTo() 这是基于坐标原点。② rQuadTo() 这是相对于上个点计算。
三阶api:①cubicTo()这是基于坐标原点。 ② rCubicTo() 这是相对于上个点计算。
各个方法都知道了,就可以开始简单操作一把了。首先创建一个类来继承下View 。
要画图肯定的准备一支笔,这里有连线还得有路径 即:Paint和path。关键代码:
private Paint mpaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Path mpath = new Path();
在构造函数里进行初始化准备,然后在onDraw()里进行绘制,代码如下:
private void init() { //设置抗锯齿 mpaint.setAntiAlias(true); //设置抗抖动 mpaint.setDither(true); mpaint.setStyle(Paint.Style.STROKE); mpaint.setStrokeWidth(10); //一阶贝塞尔 mpath.moveTo(100,100); mpath.lineTo(300,300); //二阶贝塞尔 这是基于 0 ,0 算 mpath.quadTo(500, 100, 700,300); //移动到300 ,500这个点来实现二阶贝塞尔 mpath.moveTo(300,500); //相对于实现上次的点实现二阶贝塞尔 mpath.rQuadTo(200, -300, 400,0); //三阶贝塞尔 这是基于(0,0) 移动到100 ,800 实现一个三阶贝塞尔 mpath.moveTo(100,800); mpath.cubicTo(300,600,500,1000,800,800); //移动到坐标 100,900.在实现一个 mpath.moveTo(100,900); //相对于上次的点实现三阶贝塞尔 mpath.rCubicTo(100,-200,300,200,400,0); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawPath(mpath,mpaint); }
这部分代码就实现了上面的需要绘制的5条线了。(*^__^*) 嘻嘻…… so yi z 吧!
贝塞尔推导:
有了前三阶的了解,接下来进行实现从一阶到n的推导
我们先来分析下绘制过程,贝塞尔曲线的路径与时间 t和控制点的位置有关系。看下二阶贝塞尔
假设当时间t=0.3的时候 从起始点 到控制点会出现在①的位置上,然后控制点到终点会出现在②的位置上,将两点连线后再从①到②为时间t=0.3的时候 ,即确定了图中红色点为贝塞尔曲线进过的一个点,以此类推当时间t=0.5的时候 分别充起始点到控制点,再到终端取中间的位置进行连线,然后就确定了③和④这么一条线段。再在该条线段上取中间位置,这样就确定了曲线经过的第二个点。最终大致形成的贝塞尔曲线如线图:
同理三阶贝塞尔曲线大致绘制的实现如下:
可以看出一个规律贝塞尔曲线经过的点分别是两个点两两取值后的点进行连线 直到只剩下最后一条线段,然后在该线段上取时间t下走的一段距离,便就确定了一个经过点了。嘿嘿 ,读起来是不是觉得费劲啊! 图解下吧。
有了上面的规律计算就可以搞起来了。
这里我们从代码角度来自己实现一个n阶贝塞尔 。为了验证代码的正确性,先来搞个系统自带的贝尔塞曲线,然后我们用代码实现和系统一样的效果进行填充。
假设先来实现一条下图三阶贝塞尔,然后用代码的方式将其填充其他颜色验证。
代码如下:
package com.example.administrator.androidbezier; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.os.Build; import android.support.annotation.RequiresApi; import android.util.AttributeSet; import android.view.View; /** * Created by Administrator on 2017/7/22 0022. */ public class BerzierView11 extends View { public BerzierView11(Context context) { super(context); init(); } public BerzierView11(Context context, AttributeSet attrs) { super(context, attrs); init(); } public BerzierView11(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private Paint mpaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Path mpath = new Path(); private Path srcpath = new Path(); private void init() { //设置抗锯齿 mpaint.setAntiAlias(true); //设置抗抖动 mpaint.setDither(true); mpaint.setStyle(Paint.Style.STROKE); mpaint.setStrokeWidth(10); //三阶贝塞尔 srcpath.cubicTo(200,-50,300,400,700,200); //初始化贝塞贝 new Thread(){ @Override public void run() { initBerzier(); } }.start(); } private void initBerzier() { float[] pointx = new float[]{0,200,300,700}; float[] pointy= new float[]{0, -50,400,200}; int fps =1000; for (int i=0;i<fps;i++){ float propress = i/(float)fps; float x = calculateBezier(propress,pointx); float y = calculateBezier(propress,pointy); mpath.lineTo(x,y); postInvalidate(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * * @param t 0 ~ 1的时间 * @param values 贝塞尔点集合x或y * @return 返回当前时间下的贝塞尔点 */ private float calculateBezier(float t,float ... values) { int len = values.length; for (int i = len-1;i>0;i--){ for (int j = 0 ;j<i;j++){ values[j] = values[j] + (values[j+1] -values[j])*t; } } return values[0]; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mpaint.setColor(Color.RED); canvas.drawPath(srcpath,mpaint); mpaint.setColor(Color.BLACK); canvas.drawPath(mpath,mpaint); } }
最终效果:
哈哈! 事实证明是可以的。如果想实现4阶 5阶...n 。只需要在下面两个数组里添加控制点就可以了。
float[] pointx = new float[]{0,200,300,700,...n}; float[] pointy= new float[]{0, -50,400,200,...n};
关键计算方法:每两个点取值计算后放入前一位,最后取第一个拿到我们的贝塞尔曲线点。
private float calculateBezier(float t,float ... values) { int len = values.length; for (int i = len-1;i>0;i--){ for (int j = 0 ;j<i;j++){ values[j] = values[j] + (values[j+1] -values[j])*t; } } return values[0]; }
好了代码都在上面了,今天梳理了下贝塞尔,下篇就开始贝尔塞动画实践吧!太久没做app了,心里痒痒~~~~(>_<)~~~~