viewAndroid开发社区Code For Life

贝塞尔曲线原理分析及其Android的实现

2017-05-24  本文已影响1739人  涤生_Woo

本文主要内容为贝塞尔曲线原理解析并用 SurfaceView 实现其展示动画

关于SurfaceView 的使用,大家可以看我的上一篇文章 Android:SurfaceView 的使用(附代码模板)

概述
贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。贝塞尔曲线是计算机图形学中相当重要的参数曲线,在一些比较成熟的位图软件中也有贝塞尔曲线工具,如PhotoShop等。在Flash4中还没有完整的曲线工具,而在Flash5里面已经提供出贝塞尔曲线工具。
曲线作用
由于用计算机画图大部分时间是操作鼠标来掌握线条的路径,与手绘的感觉和效果有很大的差别。即使是一位精明的画师能轻松绘出各种图形,拿到鼠标想随心所欲的画图也不是一件容易的事。这一点是计算机万万不能代替手工的工作,所以到目前为止人们只能颇感无奈。使用贝塞尔工具画图很大程度上弥补了这一缺憾。贝塞尔曲线是计算机图形图像造型的基本工具,是图形造型运用得最多的基本线条之一。

贝塞尔曲线
——来自百度百科

如果大家用过 XMind 软件或者是 WPS 软件来绘制思维导图的话,那么对下面的连线一定不会陌生。下图中如果我们要连接两个分支主题,就会用到基于三阶贝塞尔曲线的连线功能,如下所示。我们先确定需要被连接起来的起始点和终点,然后拖动两个控制点就可以任意地绘制一条连接曲线。通过这篇文章,我为大家讲解一下贝塞尔曲线的实现原理,看完本文,你也可以轻轻松松实现这个炫酷的贝塞尔曲线。


XMind 示例

贝塞尔曲线公式

以下公式中:B(t)为t时间下 点的坐标;P0为起点,Pn为终点,Pi为控制点

一阶贝塞尔曲线公式(线性公式)

给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。且其等同于线性插值。


一阶贝塞尔曲线
二阶贝塞尔曲线公式

设P0、P02、P2是一条曲线上顺序三个不同的点。过P0和P2点的两切线交于P1点,在P02点的切线交P0P1和P2P1于P01和P11,则如下比例成立:



当P0,P2固定,引入参数 t,令上述比值为 t:(1-t),即有:

将一式二式代入三式可得:

二阶贝塞尔曲线P02可以定义为分别由前两个顶点(P0,P1)和后两个顶点(P1,P2)决定的一阶贝塞尔曲线的线性组合。
可得出二阶贝塞尔曲线的公式:
三阶贝塞尔曲线公式

按照二阶的推导原理,以此类推,由四个控制点定义的三阶贝塞尔曲线P03可被定义为分别由(P0,P1,P2)和(P1,P2,P3)确定的两条二阶贝塞尔曲线的线性组合:P03 = (1-t)P02 + tP12

可递归代入得出三阶贝塞尔曲线公式:



现代的成象系统,如PostScript、Asymptote和Metafont,运用了以贝塞尔样条组成的三阶贝塞尔曲线,用来描绘曲线轮廓。

四阶及以上的贝塞尔曲线公式

由(n+1)个控制点 Pi(i=0,1,...,n) 定义的n阶贝塞尔曲线 P0n 可被定义为分别由前、后 n 个控制点定义的两条 (n-1) 阶贝塞尔曲线 P0n-1 与 P1n-1 的线性组合:


由此得到贝塞尔曲线的递推计算公式(通用公式):

其一般参数公式为:
n 阶贝塞尔曲线可如下推断。给定点P0、P1、…、Pn,其贝塞尔曲线即:

四阶贝塞尔曲线:


四阶贝塞尔曲线

五阶贝塞尔曲线:

五阶贝塞尔曲线
公式说明
  1. 开始于P0并结束于Pn的曲线,即所谓的端点插值法属性。
  1. 曲线是直线的充分必要条件是所有的控制点都位在曲线上。同样的,贝塞尔曲线是直线的充分必要条件是控制点共线。
  2. 曲线的起始点(结束点)相切于贝塞尔多边形的第一节(最后一节)。
  3. 一条曲线可在任意点切割成两条或任意多条子曲线,每一条子曲线仍是贝塞尔曲线。
  4. 一些看似简单的曲线(如圆)无法以贝塞尔曲线精确的描述,或分段成贝塞尔曲线(虽然当每个内部控制点对单位圆上的外部控制点水平或垂直的的距离为时,分成四段的贝塞尔曲线,可以小于千分之一的最大半径误差近似于圆)。
  5. 位于固定偏移量的曲线(来自给定的贝塞尔曲线),又称作偏移曲线(假平行于原来的曲线,如两条铁轨之间的偏移)无法以贝塞尔曲线精确的形成(某些琐屑实例除外)。无论如何,现存的启发法通常可为实际用途中给出近似值。

Android上实现贝赛尔曲线

在Android实现贝赛尔曲线,要借助android.graphics.Path,其中绘制贝赛尔曲线的方法在Api v1就已经提供了:

Path.moveTo(float x, float y) // Path的初始点
Path.lineTo(float x, float y) // 线性公式的贝赛尔曲线, 其实就是直线
Path.quadTo(float x1, float y1, float x2, float y2) // 二阶贝赛尔曲线
Path.cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) // 三阶贝赛尔曲线
...

这上面是Java层调用的代码,最终调用的是Skia库的一系列方法,Skia是一个C++2D向量图形处理函数库,感兴趣的可以继续深入研究研究。

实现二阶贝塞尔曲线

一、首先来看一下可操作的二阶贝塞尔曲线动态图:


二阶贝塞尔曲线

自定义 MyBezierCurveQuadratic 类代码如下,很简单:

package com.example.pc.mybeziercurveview.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * 二阶
 * Created by Deeson on 2017/5/24.
 */
public class MyBezierCurveQuadratic extends View{

    private Paint mPaint;
    private Path mPath;
    private int centerX,centerY;
    private PointF start,end,control;

    public MyBezierCurveQuadratic(Context context) {
        super(context);
        init();
    }

    public MyBezierCurveQuadratic(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyBezierCurveQuadratic(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPath = new Path();
        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);
        control = new PointF(0,0);

    }

    @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;
        control.x = centerX;
        control.y = centerY - 100;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制数据点和控制点
        mPaint.setColor(Color.GRAY);
        mPaint.setStrokeWidth(20);
        canvas.drawPoint(start.x, start.y, mPaint);
        canvas.drawPoint(end.x, end.y, mPaint);
        canvas.drawPoint(control.x, control.y, mPaint);

        //绘制辅助线
        mPaint.setStrokeWidth(4);
        canvas.drawLine(start.x,start.y,control.x,control.y,mPaint);
        canvas.drawLine(control.x, control.y, end.x, end.y, mPaint);

        //绘制二阶贝塞尔曲线
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(8);
        mPath.reset();
        mPath.moveTo(start.x,start.y);
        mPath.quadTo(control.x, control.y, end.x, end.y);
        canvas.drawPath(mPath,mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                control.x = event.getX();
                control.y = event.getY();
                invalidate();
            break;
        }
        return true;
    }
}

二、接着来看,如何用 SurfaceView 实现如下曲线动画效果:

关于SurfaceView 的使用,大家可以看我的上一篇文章 Android:SurfaceView 的使用(附代码模板)

二阶贝塞尔曲线show

这里,最关键的代码是开启子线程绘制二阶贝塞尔曲线,如下:

//辅助线坐标点
process1.x = (1 - t) * start.x + t * control.x;
process1.y = (1 - t) * start.y + t * control.y;
process2.x = (1 - t) * control.x + t * end.x;
process2.y = (1 - t) * control.y + t * end.y;

//贝塞尔曲线通用函数
x = (1 - t) * process1.x + t * process2.x;
y = (1 - t) * process1.y + t * process2.y;

mPath.lineTo(x, y);

//绘制数据点和控制点
mCanvas.drawPoint(start.x, start.y, mPointPaint);
mCanvas.drawPoint(control.x, control.y, mPointPaint);
mCanvas.drawPoint(end.x, end.y, mPointPaint);
//绘制数据点和控制点的连线
mCanvas.drawLine(start.x, start.y, control.x, control.y, mLinePaint);
mCanvas.drawLine(control.x, control.y, end.x, end.y, mLinePaint);
//绘制辅助线和辅助点
mCanvas.drawLine(process1.x, process1.y, process2.x, process2.y, mAssistLinePaint);
mCanvas.drawPoint(process1.x,process1.y,mAssistPointPaint);
mCanvas.drawPoint(process2.x,process2.y,mAssistPointPaint);
//绘制二阶贝塞尔曲线的当前点
mCanvas.drawPoint(x, y, mPointPaint);
//绘制二阶贝塞尔曲线
mCanvas.drawPath(mPath, mPaint);

自定义 QuadraticBezierShowView 类的完整代码如下:

package com.example.pc.mybeziercurveview.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * 二阶
 * Created by Deeson on 2017/5/24.
 */
public class QuadraticBezierShowView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    //分别对应贝塞尔曲线、点、数据点和控制点之间的线、辅助线、辅助点
    private Paint mPaint, mPointPaint, mLinePaint, mAssistLinePaint,mAssistPointPaint;
    //绘制贝塞尔曲线的path
    private Path mPath;
    //布局的中心点
    private int centerX, centerY;
    //分别对应贝塞尔曲线的起点、终点、控制点、辅助线的起点、终点
    private PointF start, end, control, process1, process2;

    private SurfaceHolder mHolder;
    //用于绘图的canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;

    float x = 0;//贝塞尔曲线的实时点x坐标
    float y = 0;//贝塞尔曲线的实时点y坐标
    float t = 0;//实施进度,0<=t<=1

    public QuadraticBezierShowView(Context context) {
        super(context);
        init();
    }

    public QuadraticBezierShowView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public QuadraticBezierShowView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPath = new Path();
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(5);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(60);

        mPointPaint = new Paint();
        mPointPaint.setColor(Color.BLACK);
        mPointPaint.setStrokeWidth(10);
        mPointPaint.setStyle(Paint.Style.STROKE);

        mLinePaint = new Paint();
        mLinePaint.setColor(Color.GRAY);
        mLinePaint.setStrokeWidth(4);
        mLinePaint.setStyle(Paint.Style.STROKE);

        mAssistLinePaint = new Paint();
        mAssistLinePaint.setColor(Color.GREEN);
        mAssistLinePaint.setStrokeWidth(4);
        mAssistLinePaint.setStyle(Paint.Style.STROKE);

        mAssistPointPaint = new Paint();
        mAssistPointPaint.setColor(Color.GREEN);
        mAssistPointPaint.setStrokeWidth(10);
        mAssistPointPaint.setStyle(Paint.Style.FILL);

        start = new PointF(0, 0);
        end = new PointF(0, 0);
        control = new PointF(0, 0);
        process1 = new PointF(0, 0);
        process2 = new PointF(0, 0);

        mHolder = getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
    }


    @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;
        control.x = centerX - 50;
        control.y = centerY - 300;
        x = start.x;
        y = start.y;
        mPath.moveTo(x, y);
    }


    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing = true;
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing = false;
    }

    @Override
    public void run() {
        while (mIsDrawing) {
            draw();
            if (t <= 1) {
                t += 0.003;

                //辅助线坐标点
                process1.x = (1 - t) * start.x + t * control.x;
                process1.y = (1 - t) * start.y + t * control.y;
                process2.x = (1 - t) * control.x + t * end.x;
                process2.y = (1 - t) * control.y + t * end.y;

                //贝塞尔曲线通用函数
                x = (1 - t) * process1.x + t * process2.x;
                y = (1 - t) * process1.y + t * process2.y;

                //二阶贝塞尔曲线函数
//                x = (float) (Math.pow((1 - t), 2) * start.x + 2 * t * (1 - t) * control.x + Math.pow(t, 2) * end.x);
//                y = (float) (Math.pow((1 - t), 2) * start.y + 2 * t * (1 - t) * control.y + Math.pow(t, 2) * end.y);

                mPath.lineTo(x, y);
            } else {
                mIsDrawing = false;
            }

        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            mCanvas.drawColor(Color.WHITE);
            //绘制数据点和控制点
            mCanvas.drawPoint(start.x, start.y, mPointPaint);
            mCanvas.drawPoint(control.x, control.y, mPointPaint);
            mCanvas.drawPoint(end.x, end.y, mPointPaint);
            //绘制数据点和控制点的连线
            mCanvas.drawLine(start.x, start.y, control.x, control.y, mLinePaint);
            mCanvas.drawLine(control.x, control.y, end.x, end.y, mLinePaint);
            //绘制辅助线和辅助点
            mCanvas.drawLine(process1.x, process1.y, process2.x, process2.y, mAssistLinePaint);
            mCanvas.drawPoint(process1.x,process1.y,mAssistPointPaint);
            mCanvas.drawPoint(process2.x,process2.y,mAssistPointPaint);
            //绘制二阶贝塞尔曲线的当前点
            mCanvas.drawPoint(x, y, mPointPaint);
            //绘制二阶贝塞尔曲线
            mCanvas.drawPath(mPath, mPaint);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != mCanvas) {
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }
}
实现三阶贝塞尔曲线

一、同样的,先看看可操作的三阶贝塞尔曲线:


三阶贝塞尔曲线

自定义 MyBezierCurveCubic 类的完整代码如下:

package com.example.pc.mybeziercurveview.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * 三阶
 * Created by Deeson on 2017/5/24.
 */
public class MyBezierCurveCubic extends View{

    private Paint mPaint;
    private Path mPath;
    private int centerX,centerY;
    private PointF start,end,control1,control2;
    public static final int CONTROL_ONE = 0;
    public static final int CONTROL_TWO = 1;
    private int control = CONTROL_ONE;

    public MyBezierCurveCubic(Context context) {
        super(context);
        init();
    }

    public MyBezierCurveCubic(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyBezierCurveCubic(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPath = new Path();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(8);
        mPaint.setStyle(Paint.Style.STROKE);
        start = new PointF(0,0);
        end = new PointF(0,0);
        control1 = new PointF(0,0);
        control2 = new PointF(0,0);
    }

    @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 - 200;
        control1.y = centerY - 200;
        control2.x = centerX + 200;
        control2.y = centerY + 200;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(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);
        mPath.reset();
        mPath.moveTo(start.x,start.y);
        mPath.cubicTo(control1.x,control1.y,control2.x,control2.y,end.x,end.y);
        canvas.drawPath(mPath,mPaint);
    }

    public void setControl(int control){
        this.control = control;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                if(control == CONTROL_ONE){
                    control1.x = event.getX();
                    control1.y = event.getY();
                }else{
                    control2.x = event.getX();
                    control2.y = event.getY();
                }
                invalidate();
            break;
        }
        return true;
    }
}

二、接着来看,如何用 SurfaceView 实现如下三阶贝塞尔曲线动画效果:

关于SurfaceView 的使用,大家可以看我的上一篇文章 Android:SurfaceView 的使用(附代码模板)

三阶贝塞尔曲线show

自定义 CubicBezierShowView 类的完整代码如下:

package com.example.pc.mybeziercurveview.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * 三阶
 * Created by Deeson on 2016/7/12.
 */
public class CubicBezierShowView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    //分别对应贝塞尔曲线、点、数据点和控制点之间的线、第一层辅助线、第一层辅助点、第二层辅助线、第二层辅助点
    private Paint mPaint, mPointPaint, mLinePaint, mAssistLine1Paint, mAssistPoint1Paint, mAssistLine2Paint, mAssistPoint2Paint;
    //绘制贝塞尔曲线的path
    private Path mPath;
    //布局的中心点
    private int centerX, centerY;
    //分别对应三阶贝塞尔曲线的起点、终点、控制点
    private PointF start, end, control1, control2;
    //第一层辅助线的3个端点(相当于动态的二阶贝塞尔曲线的起点,控制点,终点)
    private PointF process1, process2, process3;
    //第二层辅助线的起点和终点
    private PointF secondProcess1, secondProcess2;

    private SurfaceHolder mHolder;
    //用于绘图的canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;

    float x = 0;//贝塞尔曲线的实时点x坐标
    float y = 0;//贝塞尔曲线的实时点y坐标
    float t = 0;//实施进度,0<=t<=1

    public CubicBezierShowView(Context context) {
        super(context);
        init();
    }

    public CubicBezierShowView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CubicBezierShowView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        //贝塞尔曲线
        mPaint = new Paint();
        mPath = new Path();
        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(5);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(60);

        //点
        mPointPaint = new Paint();
        mPointPaint.setColor(Color.BLACK);
        mPointPaint.setStrokeWidth(10);
        mPointPaint.setStyle(Paint.Style.STROKE);

        //数据点和控制点的连线
        mLinePaint = new Paint();
        mLinePaint.setColor(Color.GRAY);
        mLinePaint.setStrokeWidth(4);
        mLinePaint.setStyle(Paint.Style.STROKE);

        //第一层辅助线
        mAssistLine1Paint = new Paint();
        mAssistLine1Paint.setColor(Color.GREEN);
        mAssistLine1Paint.setStrokeWidth(4);
        mAssistLine1Paint.setStyle(Paint.Style.STROKE);

        //第一层辅助点
        mAssistPoint1Paint = new Paint();
        mAssistPoint1Paint.setColor(Color.GREEN);
        mAssistPoint1Paint.setStrokeWidth(10);
        mAssistPoint1Paint.setStyle(Paint.Style.FILL);

        //第二层辅助线
        mAssistLine2Paint = new Paint();
        mAssistLine2Paint.setColor(Color.BLUE);
        mAssistLine2Paint.setStrokeWidth(4);
        mAssistLine2Paint.setStyle(Paint.Style.STROKE);

        //第二层辅助线
        mAssistPoint2Paint = new Paint();
        mAssistPoint2Paint.setColor(Color.BLUE);
        mAssistPoint2Paint.setStrokeWidth(10);
        mAssistPoint2Paint.setStyle(Paint.Style.FILL);

        //三阶贝塞尔曲线的起点终点
        start = new PointF(0, 0);
        end = new PointF(0, 0);
        //三阶贝塞尔曲线的两个控制点
        control1 = new PointF(0, 0);
        control2 = new PointF(0, 0);
        //第一层辅助线的三个端点
        process1 = new PointF(0, 0);
        process2 = new PointF(0, 0);
        process3 = new PointF(0, 0);
        //第二层辅助线的两个端点
        secondProcess1 = new PointF(0, 0);
        secondProcess2 = new PointF(0, 0);

        mHolder = getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
    }


    @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 - 150;
        control1.y = centerY - 300;
        control2.x = centerX + 170;
        control2.y = centerY - 340;
        x = start.x;
        y = start.y;
        mPath.moveTo(x, y);

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing = true;
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing = false;
    }

    @Override
    public void run() {
        while (mIsDrawing) {
            draw();
            if (t <= 1) {
                t += 0.003;
                //重点在这里
                bezierDraw();

                mPath.lineTo(x, y);
            } else {
                mIsDrawing = false;
            }

        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            mCanvas.drawColor(Color.WHITE);
            //绘制数据点和控制点
            mCanvas.drawPoint(start.x, start.y, mPointPaint);
            mCanvas.drawPoint(control1.x, control1.y, mPointPaint);
            mCanvas.drawPoint(control2.x, control2.y, mPointPaint);
            mCanvas.drawPoint(end.x, end.y, mPointPaint);
            //绘制数据点和控制点的连线
            mCanvas.drawLine(start.x, start.y, control1.x, control1.y, mLinePaint);
            mCanvas.drawLine(control1.x, control1.y, control2.x, control2.y, mLinePaint);
            mCanvas.drawLine(control2.x, control2.y, end.x, end.y, mLinePaint);
            //绘制第一层辅助线和辅助点
            mCanvas.drawLine(process1.x, process1.y, process2.x, process2.y, mAssistLine1Paint);
            mCanvas.drawLine(process2.x, process2.y, process3.x, process3.y, mAssistLine1Paint);
            mCanvas.drawPoint(process1.x, process1.y, mAssistPoint1Paint);
            mCanvas.drawPoint(process2.x, process2.y, mAssistPoint1Paint);
            mCanvas.drawPoint(process3.x, process3.y, mAssistPoint1Paint);
            //绘制第二层辅助线和辅助点
            mCanvas.drawLine(secondProcess1.x, secondProcess1.y, secondProcess2.x, secondProcess2.y, mAssistLine2Paint);
            mCanvas.drawPoint(secondProcess1.x, secondProcess1.y, mAssistPoint2Paint);
            mCanvas.drawPoint(secondProcess2.x, secondProcess2.y, mAssistPoint2Paint);
            //绘制三阶贝塞尔曲线的当前点
            mCanvas.drawPoint(x, y, mPointPaint);
            //绘制三阶贝塞尔曲线
            mCanvas.drawPath(mPath, mPaint);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != mCanvas) {
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }

    private void bezierDraw() {
        //第一层辅助线坐标点
        process1.x = (1 - t) * start.x + t * control1.x;
        process1.y = (1 - t) * start.y + t * control1.y;
        process2.x = (1 - t) * control1.x + t * control2.x;
        process2.y = (1 - t) * control1.y + t * control2.y;
        process3.x = (1 - t) * control2.x + t * end.x;
        process3.y = (1 - t) * control2.y + t * end.y;
        //第二层辅助线坐标点
        secondProcess1.x = (1 - t) * process1.x + t * process2.x;
        secondProcess1.y = (1 - t) * process1.y + t * process2.y;
        secondProcess2.x = (1 - t) * process2.x + t * process3.x;
        secondProcess2.y = (1 - t) * process2.y + t * process3.y;

        //贝塞尔曲线通用公式
        x = (1 - t) * secondProcess1.x + t * secondProcess2.x;
        y = (1 - t) * secondProcess1.y + t * secondProcess2.y;

        //三阶贝塞尔曲线函数
//        x = (float) (Math.pow((1 - t), 3) * start.x + 3 * t * Math.pow((1 - t), 2) * control1.x + 3 * Math.pow(t, 2) * (1 - t) * control2.x + Math.pow(t, 3) * end.x);
//        y = (float) (Math.pow((1 - t), 3) * start.y + 3 * t * Math.pow((1 - t), 2) * control1.y + 3 * Math.pow(t, 2) * (1 - t) * control2.y + Math.pow(t, 3) * end.y);
    }
}
最后

Demo下载地址 点这里,Demo里还有四阶曲线的展示,可以看看,原理与二阶三阶雷同,就不贴代码了。

上一篇下一篇

猜你喜欢

热点阅读