1. 程式人生 > >安卓自定義View進階-Path之貝塞爾曲線

安卓自定義View進階-Path之貝塞爾曲線

在上一篇文章Path之基本操作中我們瞭解了Path的基本使用方法,本次瞭解Path中非常非常非常重要的內容-貝塞爾曲線。


一.Path常用方法表

為了相容性(偷懶) 本表格中去除了在API21(即安卓版本5.0)以上才新增的方法。忍不住吐槽一下,為啥看起來有些順手就能寫的過載方法要等到API21才新增上啊。寶寶此刻內心也是崩潰的。

作用 相關方法 備註
移動起點 moveTo 移動下一次操作的起點位置
設定終點 setLastPoint 重置當前path中最後一個點位置,如果在繪製之前呼叫,效果和moveTo相同
連線直線 lineTo 新增上一個點到當前點之間的直線到Path
閉合路徑 close 連線第一個點連線到最後一個點,形成一個閉合區域
新增內容 addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo 新增(矩形, 圓角矩形, 橢圓, 圓, 路徑, 圓弧) 到當前Path (注意addArc和arcTo的區別)
是否為空 isEmpty 判斷Path是否為空
是否為矩形 isRect 判斷path是否是一個矩形
替換路徑 set 用新的路徑替換到當前路徑所有內容
偏移路徑 offset 對當前路徑之前的操作進行偏移(不會影響之後的操作)
貝塞爾曲線 quadTo, cubicTo 分別為二次和三次貝塞爾曲線的方法
rXxx方法 rMoveTo, rLineTo, rQuadTo, rCubicTo 不帶r的方法是基於原點的座標系(偏移量), rXxx方法是基於當前點座標系(偏移量)
填充模式 setFillType, getFillType, isInverseFillType, toggleInverseFillType 設定,獲取,判斷和切換填充模式
提示方法 incReserve 提示Path還有多少個點等待加入(這個方法貌似會讓Path優化儲存結構)
布林操作(API19) op 對兩個Path進行布林運算(即取交集、並集等操作)
計算邊界 computeBounds 計算Path的邊界
重置路徑 reset, rewind 清除Path中的內容
reset不保留內部資料結構,但會保留FillType.
rewind會保留內部的資料結構,但不保留FillType
矩陣操作 transform 矩陣變換

二.Path詳解

上一次除了一些常用函式之外,講解的基本上都是直線,本次需要了解其中的曲線部分,說到曲線,就不得不提大名鼎鼎的貝塞爾曲線。它的發明者是下面這個人(法國數學家PierreBézier)。

貝塞爾曲線能幹什麼?

貝塞爾曲線的運用是十分廣泛的,可以說貝塞爾曲線奠定了計算機繪圖的基礎(因為它可以將任何複雜的圖形用精確的數學語言進行描述),在你不經意間就已經使用過它了。

你會使用Photoshop的話,你可能會注意到裡面有一個鋼筆工具,這個鋼筆工具核心就是貝塞爾曲線。

你說你不會PS? 沒關係,你如果看過前面的文章或者用過2D繪圖,肯定繪製過圓,圓弧,圓角矩形等這些東西。這裡面的圓弧部分全部都是貝塞爾曲線的運用。

貝塞爾曲線作用十分廣泛,簡單舉幾個的栗子:

  • QQ小紅點拖拽效果
  • 一些炫酷的下拉重新整理控制元件
  • 閱讀軟體的翻書效果
  • 一些平滑的折線圖的製作
  • 很多炫酷的動畫效果

如何輕鬆入門貝塞爾曲線?

雖然貝塞爾曲線用途非常廣泛,然而目前貌似並沒有適合的中文教程,能夠搜尋出來Android關於貝塞爾曲線的中文文章基本可以分為以下幾種:

  • 科普型(只是讓人瞭解貝塞爾,並沒有實質性的內容)
  • 裝逼型(擺出來一大堆公式,引用一堆英文原文)
  • 基礎型(僅僅是講解貝塞爾曲線的兩個函式用法)
  • 實戰型(根據例項講解其中貝塞爾曲線的運用)

以上幾種型別中比較有用的就是基礎型和實戰型,但兩者各有不足,本文會綜合兩者內容,從零開始學習貝塞爾曲線。

第一步.理解貝塞爾曲線的原理

此處理解貝塞爾曲線並非是學會公式的推導過程(推倒(ノ*・ω・)ノ),而是要了解貝塞爾曲線是如何生成的。
貝塞爾曲線是用一系列點來控制曲線狀態的,我將這些點簡單分為兩類:

型別 作用
資料點 確定曲線的起始和結束位置
控制點 確定曲線的彎曲程度

此處暫時僅作了解概念,接下來就會講解其中詳細的含義。

一階曲線原理:

一階曲線是沒有控制點的,僅有兩個資料點(A 和 B),最終效果一個線段。

上圖表示的是一階曲線生成過程中的某一個階段,動態過程可以參照下圖(本文中貝塞爾曲線相關的動態演示圖片來自維基百科)。

PS:一階曲線其實就是前面講解過的lineTo。

二階曲線原理:

二階曲線由兩個資料點(A 和 C),一個控制點(B)來描述曲線狀態,大致如下:

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

連線AB BC,並在AB上取點D,BC上取點E,使其滿足條件:

連線DE,取點F,使得:

這樣獲取到的點F就是貝塞爾曲線上的一個點,動態過程如下:

PS: 二階曲線對應的方法是quadTo

三階曲線原理:

三階曲線由兩個資料點(A 和 D),兩個控制點(B 和 C)來描述曲線狀態,如下:

三階曲線計算過程與二階類似,具體可以見下圖動態效果:

PS: 三階曲線對應的方法是cubicTo

貝塞爾曲線速查表

強烈推薦點選這裡練習貝塞爾曲線,可以加深對貝塞爾曲線的理解程度。

第二步.瞭解貝塞爾曲線相關函式使用方法

一階曲線:

一階曲線是一條線段,非常簡單,可以參見上一篇文章Path之基本操作,此處就不詳細講解了。

二階曲線:

通過上面對二階曲線的簡單瞭解,我們知道二階曲線是由兩個資料點,一個控制點構成,接下來我們就用一個例項來演示二階曲線是如何運用的。

首先,兩個資料點是控制貝塞爾曲線開始和結束的位置,比較容易理解,而控制點則是控制貝塞爾的彎曲狀態,相對來說比較難以理解,所以本示例重點在於理解貝塞爾曲線彎曲狀態與控制點的關係,廢話不多說,先上效果圖:

為了更加容易看出控制點與曲線彎曲程度的關係,上圖中繪製出了輔助點和輔助線,從上面的動態圖可以看出,貝塞爾曲線在動態變化過程中有類似於橡皮筋一樣的彈性效果,因此在製作一些彈性效果的時候很常用。

主要程式碼如下:

public class Bezier extends View {

    private Paint mPaint;
    private int centerX, centerY;

    private PointF start, end, control;

    public Bessel1(Context context) {
        super(context);
        mPaint = new Paint();
        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
    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.setColor(Color.RED);
        mPaint.setStrokeWidth(8);

        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 Bezier2 extends View {

    private Paint mPaint;
    private int centerX, centerY;

    private PointF start, end, control1, control2;
    private boolean mode = true;

    public Bezier2(Context context) {
        this(context, null);

    }

    public Bezier2(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        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);
        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);
        //drawCoordinateSystem(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);
    }
}

三階曲線相比於二階曲線可以製作更加複雜的形狀,但是對於高階的曲線,用低階的曲線組合也可達到相同的效果,就是傳說中的降階。因此我們對貝塞爾曲線的封裝方法一般最高只到三階曲線。

降階與升階

型別 釋義 變化
降階 在保持曲線形狀與方向不變的情況下,減少控制點數量,即降低曲線階數 方法變得簡單,資料點變多,控制點可能減少,靈活性變弱
升階 在保持曲線形狀與方向不變的情況下,增加控制點數量,即升高曲線階數 方法更加複雜,資料點不變,控制點增加,靈活性變強

第三步.貝塞爾曲線使用例項

在製作這個例項之前,首先要明確一個內容,就是在什麼情況下需要使用貝塞爾曲線?

需要繪製不規則圖形時? 當然不是!目前來說,我覺得使用貝塞爾曲線主要有以下幾個方面(僅個人拙見,可能存在錯誤,歡迎指正)

序號 內容 用例
1 事先不知道曲線狀態,需要實時計算時 天氣預報氣溫變化的平滑折線圖
2 顯示狀態會根據使用者操作改變時 QQ小紅點,模擬翻書效果
3 一些比較複雜的運動狀態(配合PathMeasure使用) 複雜運動狀態的動畫效果

至於只需要一個靜態的曲線圖形的情況,用圖片豈不是更好,大量的計算會很不划算。

如果是顯示SVG向量圖的話,已經有相關的解析工具了(內部依舊運用的有貝塞爾曲線),不需要手動計算。

貝塞爾曲線的主要優點是可以實時控制曲線狀態,並可以通過改變控制點的狀態實時讓曲線進行平滑的狀態變化。

接下來我們就用一個簡單的示例讓一個圓漸變成為心形:

效果圖:

思路分析:

我們最終的需要的效果是將一個圓轉變成一個心形,通過分析可知,圓可以由四段三階貝塞爾曲線組合而成,如下:

心形也可以由四段的三階的貝塞爾曲線組成,如下:

兩者的差別僅僅在於資料點和控制點位置不同,因此只需要調整資料點和控制點的位置,就能將圓形變為心形。

核心難點:

1.如何得到資料點和控制點的位置?

關於使用繪製圓形的資料點與控制點早就已經有人詳細的計算好了,可以參考stackoverflow的一個回答How to create circle with Bézier curves?其中的資料只需要拿來用即可。

而對於心形的資料點和控制點,可以由圓形的部分資料點和控制點平移後得到,具體引數可以自己慢慢調整到一個滿意的效果。

2.如何達到漸變效果?

漸變其實就是每次對資料點和控制點稍微移動一點,然後重繪介面,在短時間多次的調整資料點與控制點,使其逐漸接近目標值,通過不斷的重繪介面達到一種漸變的效果。過程可以參照下圖動態效果:

程式碼:

public class Bezier3 extends View {
    private static final float C = 0.551915024494f;     // 一個常量,用來計算繪製圓形貝塞爾曲線控制點的位置

    private Paint mPaint;
    private int mCenterX, mCenterY;

    private PointF mCenter = new PointF(0,0);
    private float mCircleRadius = 200;                  // 圓的半徑
    private float mDifference = mCircleRadius*C;        // 圓形的控制點與資料點的差值

    private float[] mData = new float[8];               // 順時針記錄繪製圓形的四個資料點
    private float[] mCtrl = new float[16];              // 順時針記錄繪製圓形的八個控制點

    private float mDuration = 1000;                     // 變化總時長
    private float mCurrent = 0;                         // 當前已進行時長
    private float mCount = 100;                         // 將時長總共劃分多少份
    private float mPiece = mDuration/mCount;            // 每一份的時長


    public Bezier3(Context context) {
        this(context, null);

    }

    public Bezier3(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(8);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setTextSize(60);


        // 初始化資料點

        mData[0] = 0;
        mData[1] = mCircleRadius;

        mData[2] = mCircleRadius;
        mData[3] = 0;

        mData[
            
           

相關推薦

定義View-Path曲線

在上一篇文章Path之基本操作中我們瞭解了Path的基本使用方法,本次瞭解Path中非常非常非常重要的內容-貝塞爾曲線。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除了在API21(即安卓版本5.0)以上

定義View-Path基本操作

在上一篇Canvas之圖片文字中我們瞭解瞭如何使用Canvas中繪製圖片文字,結合前幾篇文章,Canvas的基本操作已經差不多完結了,然而Canvas不僅僅具有這些基本的操作,還可以更加炫酷,本次會了解到path(路徑)這個Canvas中的神器,有了這個神器,就能創造出更多炫(zhu

定義View-Path完結篇

經歷過前兩篇 Path之基本操作 和 Path之貝塞爾曲線 的講解,本篇終於進入Path的收尾篇,本篇結束後Path的大部分相關方法都已經講解完了,但Path還有一些更有意思的玩法,應該會在後續的文章中出現。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除

Android定義view-- 神奇的曲線

上一篇介紹了自定義view需要知道的基本函式。新開一篇獻給借給我vpn的深圳_奮鬥小哥。 轉載請註明出處:http://blog.csdn.net/wingichoy/article/details/50492828 今天給大家介紹一個非常神奇的曲線,貝塞爾曲線。相信大

定義View-Canvas圖片文字

在上一篇文章Canvas之畫布操作中我們瞭解了畫布的一些基本操作方法,本次瞭解一些繪製圖片文字相關的內容。如果你對前幾篇文章講述的內容熟練掌握的話,那麼恭喜你,本篇結束之後,大部分的自定義View已經難不倒你了,當然了,這並不是終點,接下來還會有更加炫酷的技能。 一.Canva

定義View-Canvas畫布操作

Canvas之畫布操作 上一篇Canvas之繪製基本形狀中我們瞭解瞭如何使用Canvas繪製基本圖形,本次瞭解一些基本的畫布操作。 本來想把畫布操作放到後面部分的,但是發現很多圖形繪製都離不開畫布操作,於是先講解一下畫布的基本操作方法。

定義ViewPath基本操作

在上一篇Canvas之圖片文字中我們瞭解瞭如何使用Canvas中繪製圖片文字,結合前幾篇文章,Canvas的基本操作已經差不多完結了,然而Canvas不僅僅具有這些基本的操作,還可以更加炫酷,本次會了解到path(路徑)這個Canvas中的神器,有了這個神器,就能創造出更多炫(

定義 View Path 完結篇(偽)

經歷過前兩篇 Path之基本操作 和 Path之貝塞爾曲線 的講解,本篇終於進入Path的收尾篇,本篇結束後Path的大部分相關方法都已經講解完了,但Path還有一些更有意思的玩法,應該會在後續的文章中出現吧,嗯,應該會的ˊ_>ˋ 一.Path常用方法表 為了相容性

定義ViewPath玩出花樣(PathMeasure)

PS:不要問我為什麼不講 PathEffect,因為這個方法在後面的Paint系列中。 先放一個圖鎮樓,省的下面無聊的內容把你們都嚇跑了Σ( ̄。 ̄ノ)ノ Path & PathMeasure 顧名思義,PathMeasure是一個用來測量Path的類,主要有以下方法: 構造方法 方法名 釋

定義View-手勢檢測(GestureDecetor)

Android 手勢檢測,主要是 GestureDetector 相關內容的用法和注意事項,本文依舊屬於事件處理這一體系,部分內容會涉及到之前文章提及過的知識點,如果你沒看過之前的文章,可以到 自定義 View 系列 來檢視這些內容。 在開發 Android 手機應用過程中,可

定義View-多點觸控詳解

Android 多點觸控詳解,在前面的幾篇文章中我們大致瞭解了 Android 中的事件處理流程和一些簡單的處理方案,本次帶大家瞭解 Android 多點觸控相關的一些知識。 多點觸控 ( Multitouch,也稱 Multi-touch ),即同時接受螢幕上多個點的人機互動

定義View-特殊控制元件的事件處理方案

本文帶大家瞭解 Android 特殊形狀控制元件的事件處理方式,主要是利用了 Region 和 Matrix 的一些方法,超級實用的事件處理方案,相信看完本篇之後,任何奇葩控制元件的事件處理都會變得十分簡單。 不得不說,Android 對事件體系封裝的非常棒,即便對事件體系不太

定義View-MotionEvent詳解

Android MotionEvent 詳解,之前用了兩篇文章 事件分發機制原理 和 事件分發機制詳解 來講解事件分發,而作為事件分發主角之一的 MotionEvent 並沒有過多的說明,本文就帶大家瞭解 MotionEvent 的相關內容,簡要介紹觸控事件,主要包括 單點觸控、多點

定義View-事件分發機制詳解

Android 事件分發機制詳解,在上一篇文章 事件分發機制原理 中簡要分析了一下事件分發機制的原理,原理是十分簡單的,一句話就能總結:責任鏈模式,事件層層傳遞,直到被消費。 雖然原理簡單,但是隨著 Android 不斷的發展,實際運用場景也越來越複雜,所以想要徹底玩轉事件分發機制還

定義View-Matrix Camera

本篇依舊屬於Matrix,主要講解Camera,Android下有很多相機應用,其中的美顏相機更是不少,不過今天這個Camera可不是我們平時拍照的那個相機,而是graphic包下的Camera,專業給View拍照的相機,不過既然是相機,作用都是類似的,主要是將3D的內容拍扁變成2D

定義View-Matrix詳解

這應該是目前最詳細的一篇講解Matrix的中文文章了,在上一篇文章Matrix原理中,我們對Matrix做了一個簡單的瞭解,偏向理論,在本文中則會詳細的講解Matrix的具體用法,以及與Matrix相關的一些實用技巧。 ⚠️ 警告:測試本文章示例之前請關閉硬體加速。

定義View-Matrix原理

本文內容偏向理論,和 畫布操作 有重疊的部分,本文會讓你更加深入的瞭解其中的原理。 本篇的主角Matrix,是一個一直在後臺默默工作的勞動模範,雖然我們所有看到View背後都有著Matrix的功勞,但我們卻很少見到它,本篇我們就看看它是何方神聖吧。 由於Goog

定義View-PathMeasure

可以看到,在經過 Path之基本操作 Path之貝塞爾曲線 和 Path之完結篇 後, Path中各類方法基本上都講完了,表格中還沒有講解到到方法就是矩陣變換了,難道本篇終於要講矩陣了? 非也,矩陣這一部分仍在後面單獨講解,本篇主要講解 PathMeasure 這個類與 Path 的

定義View-分類與流程

本章節為什麼要叫進階篇?(雖然講的是基礎內容),因為從本篇開始,將會逐漸揭開自定義View的神祕面紗,每一篇都將比上一篇內容更加深入,利用所學的知識能夠製作更加炫酷自定義View,就像在臺階上一樣,每一篇都更上一層,幫助大家一步步走向人生巔峰,出任CEO,迎娶白富美。 誤

定義View-縮放手勢檢測(ScaleGestureDecetor)

0. 前言 Android 縮放手勢檢測,ScaleGestureDetector 相關內容的用法和注意事項,本文依舊屬於事件處理這一體系,在大多數的情況下,縮放手勢都不是單獨存在的,需要配合其它的手勢來使用,所以推薦配合 手勢檢測(GestureDetector) 一