天天看點

安卓平滑曲線的實作:三次貝塞爾曲線

參考連結

https://www.jianshu.com/p/c4601bab860a

理論知識:

坐标計算

A0和B3連線的斜率 k = (B3Y - A0Y) / (B3X - A0X)

常數 b = A3Y - k * A3X

A2的X坐标 A2X = A3X - (A3X - A0X) * rate

A2的Y坐标 A2Y = k * A2X + b

B1的X坐标 B1X = A3X + (B3X - A3X) * rate

B1的Y坐标 B1Y = k * B1X + b

rate是一個(0, 0.5)區間内的值,數值越大,數值點之間的曲線弧度越小。

除此以外,如果數值點是第一個點或者最後一個點,可以把斜率k視為0,然後隻計算左控制點或者有控制點。

安卓平滑曲線的實作:三次貝塞爾曲線
package com.aiyuba.uiview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by maoyujiao on 2020/4/14.
 * 參考 https://www.jianshu.com/p/c4601bab860a
 * 折線的平滑處理
 * 繪制曲線,最常用的參數曲線函數就是貝塞爾曲線。
 *  二次貝塞爾曲線 quadTo
 * 三次貝塞爾曲線 cubicTo ,每個中間點的的切線上都會擴充2個點,然後以3個點畫曲線
 *
 * 令 A0表示第一個點 B3表示第三個點 A2表示第二個點
 A0和B3連線的斜率 k = (B3Y - A0Y) / (B3X - A0X)
 常數 b = A3Y - k * A3X
 則
 A2的X坐标 A2X = A3X - (A3X - A0X) * rate
 A2的Y坐标 A2Y = k * A2X + b
 B1的X坐标 B1X = A3X + (B3X - A3X) * rate
 B1的Y坐标 B1Y = k * B1X + b

 rate是一個(0, 0.5)區間内的值,數值越大,數值點之間的曲線弧度越小。

 */

public class CurveLine extends View {
    private static final String TAG = "CurveLine";
    private final ArrayList<PointF> cubicToPoints = new ArrayList<>();
    List<PointF> points = new ArrayList<>();
    Path path = new Path();
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    float rate = 0.4f;

    public CurveLine(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(4);
        initPoint();
        initControlPoint();


    }

    /**
     * //構造3次貝塞兒曲線
     */
    private void initControlPoint() {

        for (int i = 0; i < points.size() ; i++) {
            if(i == 0){
                cubicToPoints.add(points.get(i));
                //後控點
                PointF pointF_2 = new PointF();
                pointF_2.x = points.get(i).x + (points.get(i + 1).x - points.get(i).x)*rate;
                pointF_2.y = points.get(i).y;
                cubicToPoints.add(pointF_2);

            }else if(i == points.size() - 1){
                //前控點
                PointF pointF_1 = new PointF();
                pointF_1.x = points.get(i).x - (points.get(i).x - points.get(i - 1).x)*rate;
                pointF_1.y = points.get(i).y;
                cubicToPoints.add(pointF_1);
                cubicToPoints.add(points.get(i));

            }else {
                float k = (points.get(i + 1).y - points.get(i - 1).y) / (points.get(i + 1).x - points.get(i - 1).x);
                float b = points.get(i).y - k * points.get(i).x;
                Log.d(TAG, "CurveLine: k" + k + ",b" + b);

                PointF point_1 = new PointF();
                point_1.x = points.get(i).x - (points.get(i).x - points.get(i - 1).x) * rate;
                point_1.y = k * point_1.x + b;
                cubicToPoints.add(point_1);//前控制點

                cubicToPoints.add(points.get(i));//目前點

                PointF point_2 = new PointF();
                point_2.x = points.get(i).x + (points.get(i + 1).x - points.get(i).x) * rate;
                point_2.y = k * point_2.x + b;
                cubicToPoints.add(point_2);//後控制點
            }
        }
    }

    private void initPoint() {
        PointF point1 = new PointF();
        point1.set(100, 400);
        points.add(point1);

        PointF point2 = new PointF();
        point2.set(200, 300);
        points.add(point2);

        PointF point3 = new PointF();
        point3.set(300, 400);
        points.add(point3);

        PointF point4 = new PointF();
        point4.set(400, 300);
        points.add(point4);

        PointF point5 = new PointF();
        point5.set(500, 400);
        points.add(point5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        path.moveTo(points.get(0).x,points.get(0).y);
        for (int i = 0; i < points.size(); i++) {
            canvas.drawCircle(points.get(i).x,points.get(i).y,4,paint);
        }
        //每3個點畫一條貝塞爾曲線
        //從1開始,将第二個點在第一條貝塞爾曲線上。
        for (int i = 1; i < cubicToPoints.size() - 2; i+=3) {
            path.cubicTo(cubicToPoints.get(i).x,cubicToPoints.get(i).y,
                    cubicToPoints.get(i + 1).x,cubicToPoints.get(i + 1).y,
                    cubicToPoints.get(i+ 2).x,cubicToPoints.get(i + 2).y);
        }
        canvas.drawPath(path,paint);

        // 二次貝塞爾沒有經過這些點
//        for (int i = 1; i < points.size(); i++) {
//            path.quadTo(points.get(i-1).x,
//                    points.get(i-1).y,
//                    (points.get(i-1).x + points.get(i).x) / 2,
//                    (points.get(i-1 ).y + points.get(i ).y) / 2
//                    );
//        }
    }
}

           

效果圖

安卓平滑曲線的實作:三次貝塞爾曲線