Android自定義控件(二 .3)Path貝塞爾曲線

前一篇文章講了Path繪製直線以及各種基本圖形,這篇文主要說Path繪製曲線,就是曲線中非常有名的貝賽爾曲線。
貝賽爾曲線是由法國數學家Pierre Bézier所發明,由此爲計算機矢量圖形學奠定了基礎。它的主要意義在於無論是直線或曲線都能在數學上予以描述。

Paste_Image.png
貝塞爾曲線作用十分廣泛:
- QQ小紅點拖拽效果
- 一些炫酷的下拉刷新控件
- 閱讀軟件的翻書效果
- 一些平滑的折線圖的製作
- 很多炫酷的動畫效果

理解貝塞爾曲線

塞爾曲線由起始點、數據點(也稱錨點)、控制點。通過調整控制點,貝塞爾曲線的形狀會發生變化。
數據點:確定曲線的起始和結束位置
控制點:確定曲線的彎曲程度

  • 一階曲線原理:
    一階曲線是沒有控制點的,僅有兩個數據點(A 和 B),最終效果一個線段。

image.gif
- 二階曲線原理:
二階曲線由兩個數據點(A 和 C),一個控制點(B)來描述曲線狀態,大致如下:

Paste_Image.png
上圖中紅色曲線部分就是傳說中的二階貝塞爾曲線,那麼這條紅色曲線是如何生成的呢?接下來我們就以其中的一個狀態分析一下:

Paste_Image.png
連接AB BC,並在AB上取點D,BC上取點E,使其滿足條件: 

Paste_Image.png
連接DE,取點F,使得: 
這樣獲取到的點F就是貝塞爾曲線上的一個點,動態過程如下:

2222.gif
- 三階曲線原理:
三階曲線由兩個數據點(A 和 D),兩個控制點(B 和 C)來描述曲線狀態,如下:
三階曲線計算過程與二階類似,具體可以見下圖動態效果:

333.gif
貝塞爾曲線常用操作速查表

貝塞爾曲線 對應的方法 演示動畫
一階曲線(線性曲線) lineTo
二階曲線 quadTo
三階曲線 cubicTo
四階曲線
瞭解貝塞爾曲線相關函數使用方法
  • 一階曲線:參照上篇文章Path的操作
  • 二階曲線:二階曲線是由兩個數據點,一個控制點構成,兩個數據點是控制貝塞爾曲線開始和結束的位置,比較容易理解,而控制點則是控制貝塞爾的彎曲狀態,相對來說比較難以理解,所以本示例重點在於理解貝塞爾曲線彎曲狀態與控制點的關係。直接上代碼:
public class BezierTwo extends View {

    private Paint mPaint;
    private int centerX, centerY;
    private PointF start, end, control; //起點,結束點,控制點

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

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

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

    private void init() {
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(8);
        //初始化起點,結束點,控制點
        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 = centerY + 200;
        end.y = centerY;
        control.x = centerX;
        control.y = centerY - 100;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //根據觸摸點更新控制點,並重繪
        control.x = event.getX();
        control.y = event.getY();
        invalidate();
        return true;
    }

    @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(end.x,end.y,control.x,control.y,mPaint);

        //繪製貝賽爾曲線
        mPaint.setStrokeWidth(8);
        mPaint.setColor(Color.RED);
        Path path=new Path();
        path.moveTo(start.x,start.y);
        path.quadTo(control.x,control.y,end.x,end.y);
        canvas.drawPath(path,mPaint);
    }
}

效果圖如下:


爲了更直觀,上圖還繪製了控制點和輔助線,從效果圖可以看出,貝賽爾曲線是有類似於橡皮筋的效果,所以經常用於繪製一些具有彈性的效果中。
- 三階曲線:三階曲線由兩個數據點和兩個控制點來控制曲線狀態。

public class BezierThree extends View {

    private Paint mPaint;
    private int centerX, centerY;
    private PointF start, end, control1, control2;
    private boolean mode = true;

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

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

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

    private void init() {
        mPaint = new Paint();
        mPaint.setColor(Color.GRAY);
        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);
    }

    public void setMode(boolean mode) {
        this.mode = mode;
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //根據觸摸位置更新控制點並重繪
        if (mode) {
            control1.x = event.getX();
            control1.y = event.getY();
        }else {
            control2.x=event.getX();
            control2.y=event.getY();
        }
        invalidate();
        return true;
    }

    @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);

        Path path=new Path();
        path.moveTo(start.x,start.y);
        path.cubicTo(control1.x,control1.y,control2.x,control2.y,end.x,end.y);
        canvas.drawPath(path,mPaint);
    }

效果如圖:


源碼下載:https://github.com/baojie0327/ViewAndGroup

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章