Android 進階——高級UI必知必會之藉助PathMeasure打造酷炫Path特效(六)

引言

前一篇文章中總結了關於Path在UI體系當中舉足輕重的作用,Android提供了方便的API直接繪製貝塞爾曲線、數學函數、圖形組合等簡單的圖形,但假如我們想要動態的隨心所欲的截取某一段路徑或者獲取任意路徑上某一個點的座標?該如何去做呢?這篇章就總結下這方面的知識。

相關係列文件鏈接如下:

一、PathMeasure概述

有時候爲了更酷炫的效果需要去獲取Path上每一個路徑點的座標,此時就需要知道對應的數學算法(比如貝塞爾曲線上的點用的De Casteljau算法);但對於普通的的Path來說,是很難通過簡單的函數方法來進行計算的,於是Android提供了PathMeasure,(也許是太簡單,官方文檔一句介紹都沒有),顧名思義PathMeasure是一個用來“測量”Path的類。

二、PathMeasure的主要方法

1、PathMeasure的構造方法

PathMeasure的構造方法只有兩個:

  • 默認的空的構造方法創建一個空的PathMeasure對象

    可創建一個空的 PathMeasure,但是使用之前需要先調用 setPath 方法來與 Path 進行關聯且被關聯的 Path 必須是已經創建好的,而且如果關聯之後 Path 內容進行了更改,則必須使用 setPath 方法重新關聯。

  • 通過傳入已創建完畢的Path創建一個與之相關聯的PathMeasure對象

    可創建一個 PathMeasure 並自動關聯傳入的Path(和創建一個空的 PathMeasure 後調用 setPath 進行關聯效果是一樣的),同樣被關聯的 Path 也必須是已經創建好的,如果關聯之後 Path 內容進行了更改,則需要使用 setPath 方法重新關聯,forceClosed表示是否閉合傳入的路徑, true 則不論之前Path是否閉合,都會自動閉合該 Path(如果Path可以閉合的話)。

PathMeasure()	創建一個空的PathMeasure
/**
*@param forceClosed,true則表示把傳入的路徑當成閉合的來進行測量。
*/
PathMeasure(Path path, boolean forceClosed)	

注意:

  1. 不論 forceClosed 設置爲何種狀態(true 或者 false), 都不會影響原有Path的狀態,即 Path 與 PathMeasure 關聯之後,之前的的 Path 不會有任何改變。
  2. forceClosed 的設置狀態可能會影響測量結果,如果 Path 未閉合但在與 PathMeasure 關聯的時候設置 forceClosed 爲 true 時,測量結果可能會比 Path 實際長度稍長一點,獲取到到是該 Path 閉合時的狀態。

2、PathMeasure的主要方法

2.1、setPath關聯Path

void **setPath(Path path, boolean forceClosed)**用於關聯傳入的Path(已經創建完畢的Path),參數和構造方法中的作用類似。

2.2、isClosed()判斷閉合

boolean isClosed() 用於判斷關聯的路徑是否閉合,但是如果你在關聯 Path 的時設置 forceClosed 爲 true 的話,則此方法的返回值則恆爲true。

2.3、getLength獲取長度

float getLength()|獲取Path的長度,是否閉合會直接影響結果。

2.4、nextContour跳轉到下一個輪廓

boolean nextContour() 跳轉到下一個輪廓,因爲Path 可能由多條曲線構成,但不論是 getLength、還是getSegment 抑或其它方法,都只能在其中第一條線段上運行,爲了完成所有這條輪廓的測量需要跳轉到下一條曲線到方法,若跳轉成功則返回 true, 反之,失敗則返回 false。

2.5、getSegment截取路徑片段

boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 截取路徑的片段,它是PathMeasure最核心的方法,注意此處傳入的值並不是對應的座標值,各參數意義如下:

參數 作用 備註
返回值(boolean) 判斷截取是否成功 true 表示截取成功,結果存入dst中,false 截取失敗,不會改變dst中內容
startD 開始截取位置距離Path 起點的長度 取值範圍爲 0<= startD < stopD < = Path總長度
stopD 結束截取位置距離 Path 起點的長度 取值範圍爲0 <= startD < stopD <= Path總長度
dst 截取的 Path 將會添加到 dst 中 是添加而不是替換
startWithMoveTo 起始點是否使用 moveTo 用於保證截取的 Path 第一個點位置不變

使用getSegment需要注意以下幾點:

  • 如果 startD、stopD 的數值不在取值範圍 [0, getLength] 內或者 startD == stopD ,則返回值爲 false且不會改變 dst 內容。
  • 使用以下規則來決定 startWithMoveTo 的取值,爲了保證截取得到的 Path 片段不會發生形變則取true;而保證存儲截取片段的 Path(dst) 的連續性則取false

如果在安卓4.4或者之前的版本,在默認開啓硬件加速的情況下,更改 dst 的內容後可能繪製會出現問題,請關閉硬件加速或者給 dst 添加一個單個操作(如:dst.rLineTo(0, 0))

2.6、getPosTan獲取指定長度的位置座標及該點切線值tangle

boolean getPosTan(float distance, float[] pos, float[] tan) 獲取指定長度的位置座標及該點切線值tangle(正切值)。

參數 作用 備註
返回值(boolean) 判斷獲取是否成功 true表示成功,數據會存入 pos 和 tan 中,false 表示失敗,pos 和 tan 不會改變
distance 距離 Path 起點的長度 取值範圍: 0 <= distance <= getLength
pos 該點的座標值 座標值: (x==[0], y==[1])
tan 該點的正切值 正切值: (x==[0], y==[1])

通過 tan 得值計算出圖片旋轉的角度,tan 是 tangent 的縮寫,即中學中常見的正切, 其中tan0是鄰邊邊長,tan1是對邊邊長,而Math中 atan2 方法是根據正切是數值計算出該角度的大小,得到的單位是弧度,所以上面又將弧度轉爲了角度。

2.7、getMatrix獲取指定長度的位置座標及該Matrix(矩陣)

**boolean getMatrix(float distance, Matrix matrix, int flags)**獲取指定長度的位置座標及該Matrix(矩陣).

參數 作用 備註
返回值(boolean) 判斷獲取是否成功 true表示成功,數據會存入matrix中,false 失敗,matrix內容不會改變
distance 距離 Path 起點的長度 取值範圍: 0 <= distance <= getLength
matrix 根據 falgs 封裝好的matrix 會根據 flags 的設置而存入不同的內容
flags 規定哪些內容會存入到matrix中 可選擇POSITION_MATRIX_FLAG(位置) 或ANGENT_MATRIX_FLAG(正切)

其實源碼沒啥好看的,核心操作都是在native層去完成的,Orz…

public class PathMeasure {
    private Path mPath;

    public PathMeasure() {
        mPath = null;
        native_instance = native_create(0, false);
    }

    public PathMeasure(Path path, boolean forceClosed) {
        // The native implementation does not copy the path, prevent it from being GC'd
        mPath = path;
        native_instance = native_create(path != null ? path.readOnlyNI() : 0,
                forceClosed);
    }

    /**
     *  關聯一個Path
     */
    public void setPath(Path path, boolean forceClosed) {
        mPath = path;
        native_setPath(native_instance,
                path != null ? path.readOnlyNI() : 0,
                forceClosed);
    }

    /**
     * 返回當前輪廓的總長度,或者如果沒有路徑,則返回0。與此度量對象相關聯。
     */
    public float getLength() {
        return native_getLength(native_instance);
    }

    /**
     *  獲取指定長度的位置座標及該點切線值
     * @param distance The distance along the current contour to sample 位置
     * @param pos If not null, returns the sampled position (x==[0], y==[1]) 座標值
     * @param tan If not null, returns the sampled tangent (x==[0], y==[1])  切線值
     * @return false if there was no path associated with this measure object
     */
    public boolean getPosTan(float distance, float pos[], float tan[]) {
        if (pos != null && pos.length < 2 ||
                tan != null && tan.length < 2) {
            throw new ArrayIndexOutOfBoundsException();
        }
        return native_getPosTan(native_instance, distance, pos, tan);
    }

    public static final int POSITION_MATRIX_FLAG = 0x01;    // must match flags in SkPathMeasure.h
    public static final int TANGENT_MATRIX_FLAG  = 0x02;    // must match flags in SkPathMeasure.h

    /**
     * @param distance The distance along the associated path
     * @param matrix Allocated by the caller, this is set to the transformation
     *        associated with the position and tangent at the specified distance
     * @param flags Specified what aspects should be returned in the matrix.
     */
    public boolean getMatrix(float distance, Matrix matrix, int flags) {
        return native_getMatrix(native_instance, distance, matrix.native_instance, flags);
    }
    
    public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) {
        // Skia used to enforce this as part of it's API, but has since relaxed that restriction
        // so to maintain consistency in our API we enforce the preconditions here.
        float length = getLength();
        if (startD < 0) {
            startD = 0;
        }
        if (stopD > length) {
            stopD = length;
        }
        if (startD >= stopD) {
            return false;
        }

        return native_getSegment(native_instance, startD, stopD, dst.mutateNI(), startWithMoveTo);
    }

    /**
     *  是否閉合
     */
    public boolean isClosed() {
        return native_isClosed(native_instance);
    }

    /**
     * Move to the next contour in the path. Return true if one exists, or
     * false if we're done with the path.
     */
    public boolean nextContour() {
        return native_nextContour(native_instance);
    }

    protected void finalize() throws Throwable {
        native_destroy(native_instance);
        native_instance = 0;  // Other finalizers can still call us.
    }

    private static native long native_create(long native_path, boolean forceClosed);
    private static native void native_setPath(long native_instance, long native_path, boolean forceClosed);
    private static native float native_getLength(long native_instance);
    private static native boolean native_getPosTan(long native_instance, float distance, float pos[], float tan[]);
    private static native boolean native_getMatrix(long native_instance, float distance, long native_matrix, int flags);
    private static native boolean native_getSegment(long native_instance, float startD, float stopD, long native_path, boolean startWithMoveTo);
    private static native boolean native_isClosed(long native_instance);
    private static native boolean native_nextContour(long native_instance);
    private static native void native_destroy(long native_instance);

    /* package */private long native_instance;

未完待續…

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