美文网首页
贝塞尔曲线入门

贝塞尔曲线入门

作者: 黄烨1121 | 来源:发表于2017-12-01 01:32 被阅读0次

    本章目录

    • Part One:一阶贝塞尔曲线
    • Part Two:二阶贝塞尔曲线
    • Part Three:三阶贝塞尔曲线

    贝塞尔曲线由法国工程师皮埃尔.贝塞尔所广泛发表,他运用贝济埃曲线来为汽车的主体进行设计,现在成为计算机图形学中相当重要的参数曲线。
    贝塞尔曲线的原理就是利用多个点的位置来确定出一条曲线。这多个点就是起点,终点,控制点。控制点可以没有,也可以有多个。由这条曲线来确定View的移动轨迹,从而绘制出一些酷炫的动画。
    贝塞尔曲线可以有很多阶,一般来说,我们用二阶和三阶比较多。相应的,Android官方也提供了1到3阶贝塞尔曲线的api。
    四阶以上的比较少见,如果确实有需要的,可以去网上搜相应的算法。
    首先,我们先从最简单的一阶贝塞尔曲线开始看

    Part One:一阶贝塞尔曲线

    一阶贝塞尔曲线也叫线性曲线,该函数中的t会经过由P0至P1的B(t)所描述的曲线。例如当t=0.25时,B(t)即一条由点P0至P1路径的四分之一处。就像由0至1的连续t,B(t)描述一条由P0至P1的直线。

    一阶贝塞尔曲线.gif
    注:图片来源于维基百科
    它的公式是
    image.png

    具体代码为

    package com.terana.mycustomview.customview;
    
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.graphics.Point;
    import android.support.annotation.Nullable;
    import android.util.AttributeSet;
    import android.view.View;
    
    public class HeartView extends View {
        private Paint linePaint;
        private Paint moveLinePaint;
        private Path linePath;
        private Path moveLinePath;
        private Point startPoint;
        private Point endPoint;
    
        public HeartView(Context context) {
            this(context, null);
        }
    
        public HeartView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public HeartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            this(context, attrs, defStyleAttr, 0);
        }
    
        public HeartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            initVariables();
        }
    
        private void initVariables() {
            //静止曲线画笔
            linePaint = new Paint();
            linePaint.setAntiAlias(true);//抗锯齿
            linePaint.setDither(true);//防抖动
            linePaint.setStyle(Paint.Style.STROKE);//绘制样式
            linePaint.setStrokeWidth(5);//线条粗细
            linePaint.setColor(Color.GRAY);//灰色线条
    
            // 运动曲线画笔
            moveLinePaint = new Paint();
            moveLinePaint.setAntiAlias(true);//抗锯齿
            moveLinePaint.setDither(true);//防抖动
            moveLinePaint.setStyle(Paint.Style.STROKE);//绘制样式
            moveLinePaint.setStrokeWidth(5);//线条粗细
            moveLinePaint.setColor(Color.RED);//红色线条
    
            //静止线条路径
            linePath = new Path();
    
            //运动线条路径
            moveLinePath = new Path();
    
            //起始点坐标
            startPoint = new Point();
            startPoint.set(100, 100);
    
            //终止点坐标
            endPoint = new Point();
            endPoint.set(400, 400);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            linePath.moveTo(startPoint.x, startPoint.y);
            linePath.lineTo(endPoint.x, endPoint.y);//一阶贝塞尔曲线线条
            canvas.drawPath(linePath, linePaint);
            drawMovePath(canvas);
        }
    
        private float moveX = 100f, moveY = 100f;
        private long delay = 20;
        private long period = 1000;
    
        private void drawMovePath(Canvas canvas) {
            moveLinePath.moveTo(startPoint.x, startPoint.y);
            moveLinePath.lineTo(moveX, moveY);
            canvas.drawPath(moveLinePath, moveLinePaint);
            getHandler().postDelayed(runnable, delay);
            if (i == (period / delay + 1)){
                getHandler().removeCallbacks(runnable);
            }
        }
    
        private int i = 0;
        private Runnable runnable = new Runnable() {
            @Override
            public void run() {
                moveX = calculateDimension(true, i);
                moveY = calculateDimension(false, i);
                invalidate();
                i++;
            }
        };
    
        private float calculateDimension(boolean flag, int index) {
            float result;
            float t = delay * index * 1.0f / period;
            if (flag) {
                result = (1 - t) * startPoint.x + t * endPoint.x;
            } else {
                result = (1 - t) * startPoint.y + t * endPoint.y;
            }
            return result;
        }
    }
    

    Part Two:二阶贝塞尔曲线

    二阶贝塞尔曲线的路径由给定点P0、P1、P2的函数B(t)追踪:


    image.png
    二阶贝萨尔曲线.gif

    自定义View布局部分

    package com.terana.mycustomview.customview;
    
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.graphics.Point;
    import android.support.annotation.Nullable;
    import android.util.AttributeSet;
    import android.view.View;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class HeartView extends View {
        private Paint linePaint;
        private Paint moveLinePaint;
        private Path linePath;
        private Path moveLinePath;
        private Point startPoint;
        private Point endPoint;
        private Point controlPointOne;
    
        public HeartView(Context context) {
            this(context, null);
        }
    
        public HeartView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public HeartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            this(context, attrs, defStyleAttr, 0);
        }
    
        public HeartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            initVariables();
        }
    
        private void initVariables() {
            //静止曲线画笔
            linePaint = new Paint();
            linePaint.setAntiAlias(true);//抗锯齿
            linePaint.setDither(true);//防抖动
            linePaint.setStyle(Paint.Style.STROKE);//绘制样式
            linePaint.setStrokeWidth(5);//线条粗细
            linePaint.setColor(Color.GRAY);//灰色线条
    
            // 运动曲线画笔
            moveLinePaint = new Paint();
            moveLinePaint.setAntiAlias(true);//抗锯齿
            moveLinePaint.setDither(true);//防抖动
            moveLinePaint.setStyle(Paint.Style.STROKE);//绘制样式
            moveLinePaint.setStrokeWidth(5);//线条粗细
            moveLinePaint.setColor(Color.RED);//红色线条
    
            //静止线条路径
            linePath = new Path();
    
            //运动线条路径
            moveLinePath = new Path();
    
            //起始点坐标
            startPoint = new Point();
            startPoint.set(100, 100);
    
            //终止点坐标
            endPoint = new Point();
            endPoint.set(400, 400);
    
            //控制点1坐标
            controlPointOne = new Point();
            controlPointOne.set(600, 100);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            linePath.moveTo(startPoint.x, startPoint.y);
            //linePath.lineTo(endPoint.x, endPoint.y);//一阶贝塞尔曲线线条
            linePath.quadTo(controlPointOne.x, controlPointOne.y, endPoint.x, endPoint.y);//二阶贝塞尔曲线
            canvas.drawPath(linePath, linePaint);
            drawMovePath(canvas);
        }
    
        private List<Map<String, Float>> pointList = new ArrayList<>();
        private static final String XPOSITION = "xPosition";
        private static final String YPOSITION = "yPosition";
    
        private void drawMovePath(final Canvas canvas) {
            moveLinePath.moveTo(startPoint.x, startPoint.y);
            for (int index = 0; index < pointList.size(); index++){
                if (index > 0){
                    canvas.drawLine(pointList.get(index - 1).get(XPOSITION), pointList.get(index - 1).get(YPOSITION),
                            pointList.get(index).get(XPOSITION), pointList.get(index).get(YPOSITION), moveLinePaint);
                }
            }
        }
    
        public Point getStartPoint() {
            return startPoint;
        }
    
        public Point getEndPoint() {
            return endPoint;
        }
    
        public Point getControlPointOne() {
            return controlPointOne;
        }
    
        public void setPointList(float currentX, float currentY){
            Map<String, Float> temp = new HashMap<>();
            temp.put(XPOSITION, currentX);
            temp.put(YPOSITION, currentY);
            pointList.add(temp);
            invalidate();
        }
    }
    

    Activity部分:

    package com.terana.mycustomview.activities;
    
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    
    import com.terana.mycustomview.R;
    import com.terana.mycustomview.customview.HeartView;
    
    public class HeartActivity extends BaseActivity {
        private static Handler handler;
        private HeartView heartView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            removeStatusBar();
            setContentView(R.layout.activity_heart);
            initViews();
            updatePosition();
        }
    
        float xPosition = 100f;
        float yPosition = 100f;
    
        private void updatePosition() {
    
            new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        float t = i * 1.0f / 1000;
                        Message message = new Message();
                        message.what = 100;
                        Bundle bundle = new Bundle();
                        bundle.putFloat("x", xPosition);
                        bundle.putFloat("y", yPosition);
                        message.setData(bundle);
                        handler.sendMessage(message);
                        try {
                            sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        xPosition = (1 - t) * (1 - t) * heartView.getStartPoint().x + 2 * t * (1 - t) * heartView.getControlPointOne().x
                        + t * t * heartView.getEndPoint().x;
                        yPosition = (1 - t) * (1 - t) * heartView.getStartPoint().y + 2 * t * (1 - t) * heartView.getControlPointOne().y
                                + t * t * heartView.getEndPoint().y;
                    }
                }
            }.start();
        }
    
        private void initViews() {
            heartView = findViewById(R.id.heartView_heart);
            handler = new MyHandler();
        }
    
        private class MyHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 100:
                        Bundle bundle = msg.getData();
                        heartView.setPointList(bundle.getFloat("x"), bundle.getFloat("y"));
                    default:
                        break;
                }
                super.handleMessage(msg);
            }
        }
    }
    

    Part Three:三阶贝塞尔曲线

    P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝塞尔曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P2之前,走向P1方向的“长度有多长”。
    曲线的参数形式为:


    image.png
    三阶贝萨尔曲线.gif

    自定义View部分:

    package com.terana.mycustomview.customview;
    
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.graphics.Point;
    import android.support.annotation.Nullable;
    import android.util.AttributeSet;
    import android.view.View;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class HeartView extends View {
        private Paint linePaint;
        private Paint moveLinePaint;
        private Path linePath;
        private Path moveLinePath;
        private Point startPoint;
        private Point endPoint;
        private Point controlPointOne;
        private Point controlPointTwo;
    
        public HeartView(Context context) {
            this(context, null);
        }
    
        public HeartView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public HeartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            this(context, attrs, defStyleAttr, 0);
        }
    
        public HeartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            initVariables();
        }
    
        private void initVariables() {
            //静止曲线画笔
            linePaint = new Paint();
            linePaint.setAntiAlias(true);//抗锯齿
            linePaint.setDither(true);//防抖动
            linePaint.setStyle(Paint.Style.STROKE);//绘制样式
            linePaint.setStrokeWidth(5);//线条粗细
            linePaint.setColor(Color.GRAY);//灰色线条
    
            // 运动曲线画笔
            moveLinePaint = new Paint();
            moveLinePaint.setAntiAlias(true);//抗锯齿
            moveLinePaint.setDither(true);//防抖动
            moveLinePaint.setStyle(Paint.Style.STROKE);//绘制样式
            moveLinePaint.setStrokeWidth(5);//线条粗细
            moveLinePaint.setColor(Color.RED);//红色线条
    
            //静止线条路径
            linePath = new Path();
    
            //运动线条路径
            moveLinePath = new Path();
    
            //起始点坐标
            startPoint = new Point();
            startPoint.set(100, 100);
    
            //终止点坐标
            endPoint = new Point();
            endPoint.set(400, 400);
    
            //控制点1坐标
            controlPointOne = new Point();
            controlPointOne.set(600, 100);
    
            //控制点2坐标;
            controlPointTwo = new Point();
            controlPointTwo.set(100, 300);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            linePath.moveTo(startPoint.x, startPoint.y);
            //linePath.lineTo(endPoint.x, endPoint.y);//一阶贝塞尔曲线线条
            linePath.cubicTo(controlPointOne.x, controlPointOne.y, controlPointTwo.x, controlPointTwo.y, endPoint.x, endPoint.y);//二阶贝塞尔曲线
            canvas.drawPath(linePath, linePaint);
            drawMovePath(canvas);
        }
    
        private List<Map<String, Float>> pointList = new ArrayList<>();
        private static final String XPOSITION = "xPosition";
        private static final String YPOSITION = "yPosition";
    
        private void drawMovePath(final Canvas canvas) {
            moveLinePath.moveTo(startPoint.x, startPoint.y);
            for (int index = 0; index < pointList.size(); index++) {
                if (index > 0) {
                    canvas.drawLine(pointList.get(index - 1).get(XPOSITION), pointList.get(index - 1).get(YPOSITION),
                            pointList.get(index).get(XPOSITION), pointList.get(index).get(YPOSITION), moveLinePaint);
                }
            }
        }
    
        public Point getStartPoint() {
            return startPoint;
        }
    
        public Point getEndPoint() {
            return endPoint;
        }
    
        public Point getControlPointOne() {
            return controlPointOne;
        }
    
        public Point getControlPointTwo() {
            return controlPointTwo;
        }
    
        public void setPointList(float currentX, float currentY) {
            Map<String, Float> temp = new HashMap<>();
            temp.put(XPOSITION, currentX);
            temp.put(YPOSITION, currentY);
            pointList.add(temp);
            invalidate();
        }
    }
    

    Avtivity部分:

    package com.terana.mycustomview.activities;
    
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    
    import com.terana.mycustomview.R;
    import com.terana.mycustomview.customview.HeartView;
    
    public class HeartActivity extends BaseActivity {
        private static Handler handler;
        private HeartView heartView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            removeStatusBar();
            setContentView(R.layout.activity_heart);
            initViews();
            updatePosition();
        }
    
        float xPosition = 100f;
        float yPosition = 100f;
    
        private void updatePosition() {
    
            new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        float t = i * 1.0f / 1000;
                        Message message = new Message();
                        message.what = 100;
                        Bundle bundle = new Bundle();
                        bundle.putFloat("x", xPosition);
                        bundle.putFloat("y", yPosition);
                        message.setData(bundle);
                        handler.sendMessage(message);
                        try {
                            sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        xPosition = (float) (Math.pow(1 - t, 3) * heartView.getStartPoint().x
                                + 3 * heartView.getControlPointOne().x * t * Math.pow(1 - t, 2)
                                + 3 * heartView.getControlPointTwo().x * Math.pow(t, 2) * (1 - t)
                                + Math.pow(t, 3) * heartView.getEndPoint().x);
                        yPosition = (float) (Math.pow(1 - t, 3) * heartView.getStartPoint().y
                                + 3 * heartView.getControlPointOne().y * t * Math.pow(1 - t, 2)
                                + 3 * heartView.getControlPointTwo().y * Math.pow(t, 2) * (1 - t)
                                + Math.pow(t, 3) * heartView.getEndPoint().y);
                    }
                }
            }.start();
        }
    
        private void initViews() {
            heartView = findViewById(R.id.heartView_heart);
            handler = new MyHandler();
        }
    
        private class MyHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 100:
                        Bundle bundle = msg.getData();
                        heartView.setPointList(bundle.getFloat("x"), bundle.getFloat("y"));
                    default:
                        break;
                }
                super.handleMessage(msg);
            }
        }
    }
    

    最终效果为:


    三阶贝塞尔曲线效果图.gif

    其实三阶的会了,高阶的也差不多会了,就是控制点多了点,数学算法更复杂了一点,网上有模型,把公式复制过来即可,就不再多说了。

    相关文章

      网友评论

          本文标题:贝塞尔曲线入门

          本文链接:https://www.haomeiwen.com/subject/jvtibxtx.html