android高級UI之PathMeasure<一>--Path測量基礎(nextContour、getPosTan、getMatrix、getSegment)


前言:

在上一次https://www.cnblogs.com/webor2006/p/15265808.html完成了對於貝塞爾曲線繪制的學習,今天准備學習UI繪制中經常會用到的跟Path相關的一些知識,也是很重要,但是你不去專門花時間去研究的話其實理解起來也並不輕松,關於掌握了這個技能之后最終你能做出啥UI效果呢?其實很多,這里先提前把要操練的一個效果貼出來,先來感受一下:

 

其中圓是使用Paint繪制出來的,而那個箭頭是一張靜態圖片,如果在你沒有了解PathMeasure它的使用之前,碰到這樣的效果是不是有點懵,除了叫UI給出這個效果的一些幀圖片出來,然后你用幀動畫播出來,是不是沒有更好的思路?而學會了PathMeasure它的使用之后,實際利用它還能做出更多效果,再貼一個之后咱們要實現的效果:

是不是非常常見的一個Loading效果,但是這里同樣要求是用純繪制的方式來實現,而非使用幀動畫喲。學好PathMeasure是實現上述效果的一個基礎,由於這塊我完全陌生,所以打算會花幾個篇幅好好的學一下它。

PathMeasure核心API了解:

類縱覽:

從這個類名就知道這類的作用---路徑測量【其中關於Path的繪制在之前https://www.cnblogs.com/webor2006/p/7341697.html已經學過了】,具體怎么個測量法,往下學習就知道了,先來大致瞅了一下這個類的一些核心方法:

整體的源碼170多行不是很多,但是!!!完全木有用過,所以在正式進行案例的編寫之前,先得對這些方法進行一定的了解才行。

setPath()、getLength():

API了解:

其中還可以在構造中進行path的指定:

其中有一個參數“forceClosed”,強制關閉,這是干嘛用的呢?關於它的理解需要用下面的代碼實驗進行理解。

另外getLenth()就不多說了,測量后Path的整個長度的計算方法。

實踐: 

接下來則用代碼執行效果的角度直觀的理解一下這些API。

1、構建一個Path:

既然要對Path測量,肯定得先有Path才行對吧,所以先來構建一個簡單的Path:

package com.cexo.pathmeasurestudy;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

/**
 * PathMeasure API了解
 */
public class MyView extends View {

    private Paint defaultPaint;

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        defaultPaint = new Paint();
        defaultPaint.setColor(Color.RED);
        defaultPaint.setStrokeWidth(5);
        defaultPaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        testForceClosed(canvas);
    }

    /**
     * PathMeasure.setPath api了解
     */
    private void testForceClosed(Canvas canvas) {
        Path path = new Path();

        path.lineTo(0, 200);
        path.lineTo(200, 200);
        path.lineTo(200, 0);

        canvas.drawPath(path, defaultPaint);

    }
}

此時運行:

目前坐標點為(0,0)的位置,為了方便觀察,咱們將坐標點移到屏幕中間來吧:

再運行:

其中可以看到,對於一個正方形,還差一條上邊線對吧,這是有意為之的,因為為了之后理解"forceClosed"參數。

2、構建一個坐標系:

為了更加清晰的知道坐標點在哪,這里構建一個坐標輔助線:

此時的效果:

3、理解forceClosed參數的含義:

有了Path之后,接下來就可以使用PathMeasure對它進行測量了,如下:

其中可以發現,如果forceClosed指定為false時,其PathMeasure測量的就是Path的實際長度,因為咱們這個Path就三條線,每條線都是200長,剛好測量出來的Path的總長度為600。

好,接下來咱們將forceClosed改為true,然后再看一下測量結果:

變成四條邊的長度了,怎么樣,此時你能理解forceClosed的含義了么?應該不難了,當它為真時,表示在測量Path時會自動閉合然后再進行測對吧,其中“自動閉合”這個關鍵字標紅了,是因為有細節需要挖掘一下,對於咱們這個例子是很好理解這個閉合的:

但是!!!要注意了,雖說PathMeasure在測量時主動給加上了上邊缺的這條邊的長度,可最終繪制還是按咱們Path所指定的來,也就是PathMeasure是不會改變Path的繪制結果的,PathMeasure只是一個工具,這一點需要明白。那如果我們的Path是一條直線而非一個可以閉合的正方形呢?咱們可以進一步再來體會一下“自動閉合”的含義:

此時的形態為:

原來一條直線在閉合狀態下,其長度也變成了2條邊的長度了?其實對於Path中的一條直線,當閉合時是這么走了一圈:

所以兩條邊加起來就是400了,關於"forceClosed"參數的作用這就明白了。 

接下來再從應用效果上來理解一下"forceClosed",對於咱們目前這個路徑,如果在測量時指定forceClosed=false情況下,如果有一個圖片你想讓它按着咱們的Path繪制軌跡來走的話,“應該”就是這樣了:

而當“forceClosed=true”時,其運行軌跡就為:

其中說的是“應該”,是不是真的如此呢?由於目前還沒有能力做出這種效果,待篇末自已來實現這樣的效果再來確認這種“應該”。

nextContour():

API了解:

先來看一下官方API的解釋:

你能理解么?很顯然是理解不了,啥叫“輪廓”,所以接下來還得以代碼的角度來進行理解。其中“Contour”是“輪廓”的意思。

實踐:

1、多個Path疊加知識了解:

為了理解這個API,這里需要構建兩個Path, 然后會調用Path的圖形疊加的API:

其中第三個參數就有一個模式設置:

具體有啥作用,直接看下面的構建效果就明白了。

2、構建Path:

這里構建兩個Path,具體如下:

 

此時運行的效果如下:

其中:

3、通過打印長度來觀察nextContour的作用:

好,接下來咱們則利用PathMeasure對這兩個Path進行一個測量,看一下整個Path的長度是多少:

 

那如果想把里面那個Path也算進來,此時就需要使用它了:

理解了么?其實也就是默認PathMeasure只會測最外面的Path,對於它里面的,如果你也想測到,就需要調用一下nextContour轉到里面的Path進行測量。

getPosTan():

API了解:

先來上官網了解一下它:

其中這里有一個“切線”的概念,這里其實是初中數學的概念,先來復習理解一下,先度娘一下,這里以圓的切線為例:

拿圖來理解:

然后經過圓心的線經過切點A:

然后切線就是指經過切點A且這條經垂直的這條線:

其中對於咱們這個API不是有兩個數組參數么?

其實它們就是用來獲取坐標點的,其中pos參數是獲取Path指定distance位置的坐標點,也是就指圖中的A點,而tan參數是指圓心點,也就是:

那,咱們現在代碼中運行的效果中,不是圓心,而是缺一條邊的正方形呀,其實道理是一樣的,下面用代碼來實驗一下就明白了。

實踐:

此時運行一下:

有個細節可能會有疑問,就是對於外面的這個路徑我們指定的是獲取50長度的位置對吧,也就是這段長度:

呃,貌似我們在繪制矩形的時候不是按順時針的么?

等於這個API是按這么個順序來進行位置的計算的:

那如果我們將其改為逆時針,最終效果又會是咋樣呢?

哦,原來是按這么個順序:

關於這個小細節,提前了解一下。  

getMatrix():

API了解:

先來看一下官方的說明:

你會發現貌似跟沒解釋一樣,還是不理解它的作用, 這里理解起來會稍微麻煩一些,還是以一個實際場景為例吧,以下面咱們既將要實踐的這個效果為例:

其中這個箭頭想要沿着這個圓的軌跡走,是不是得讓圖片不斷地進行角度變化才行?而對於一個圖片在繪制時要轉換它的方向有這么一個API:

而這個matrix的值就可以調用這個API來幫你獲得,它直接幫你算了角度並且幫你進行了旋轉,而這個API的使用也非常之簡單,傳你想要進行旋轉在path上的位置既可:

其中第二個參數matrix就是幫我們來獲取相關角度信息用的,我們new一個傳進來既可,最后一個flags傳這倆就成:

它表示用於指定哪些內容會存入 matrix 中。flags 的值有兩個:
pathMeasure.POSITION_MATRIX_FLAG 表示獲取位置信息;
pathMeasure. TANGENT_MATRIX_FLAG 表示獲取切邊信息,使得圖片按Path 旋轉。

好,關於這塊的實踐就放在最后的圖片按圓軌跡的旋轉案例當中,這里就先簡單介紹一下其API的使用方式。

如何自己來實現角度旋轉算法:

目前我們已經知道調用getMatrix() API來獲取指定path位置點的旋轉角度信息對吧,那如果沒有這個api,要你自己來算圓上指定點的角度有沒有思路呢?其實有辦法,目前咱們不是通過PathMeasure可以獲取這倆值了么?

要想讓圖片旋轉移動,很明顯這里有兩個動作:旋轉+移動,有了pos信息,最起碼移動就簡單了,直接用matrix中的這個方法既可:

那接下來就是讓圖片進行角度旋轉了,其實也就是調matrix的這個方法:

這里需要傳三個參數對吧,先來解決容易的這倆:

所以這倆參數應該是這樣傳既可:

現在就剩最后一個難點了,第一個參數需要傳旋轉的“角度”,其實也不難,因為在之前https://www.cnblogs.com/webor2006/p/7687320.htmlUI的一個案例效果中已經詳細的剖析過了,這里回憶一下其計算過程:

 

當時(x,y)取的是圓里面的坐標,而我們目前是在圓邊上的坐標,其實差不多,然后斜率公式為:

 

這不就是tangent正切值么?這里百度百科一下啥叫正切值:

而我們已知正切值了:

也就是知道了斜率了,接下來再回憶一下當時是怎么通過斜率來計算角度的:

哦,先根據斜率利用反正切函數來計算出對應的“弧度”,注意不是“角度”喲,其實Math.atan還有另一個較方便的版本:

所以咱們這里的弧度計算就可以是Math.atan2(tan[1],tan[0])對吧,有了弧度了,再算角度困難么?

 

而對於這個我們也沒必要自己算,因為Math類中已有一個現成轉角度的方法:

至此,要你自己來實現圖片圍繞一個圓來旋轉的效果還難么?這里相當於重新復習了一下之前所學的“舊”知識,這里也再一次說明:“你平常所學的看似不怎么起眼好像沒啥用的各種“零碎”的知識,用心學習之后,在未來的某個時刻會發光發亮,任何知識都不是孤獨存在的”,所以平常學習中發現某個知識好像跟自己工作並無太大關系時,不要糾結,要學就學透,學扎實,總有一天會派上用場的,堅信這一點。

getSegment():

API了解:

先來看一下官方解釋:

嗯,基本從這API的說明也大致能了解它的用處,好像就是從咱們的Path中來截取指定的一段對吧,是的,用這個API就能實現開篇所展示的這個效果:

其中繪制的是一個整圓,只是通過這個API,咱們動態的截取了其中的一段才產生了這樣的效果,因為很明顯它也是按着一個圓的軌跡再跑對吧,所以還是跟Path測量有關。只是目前對於第四個參數"startWithMoveTo"有點不太理解,沒關系,下面用代碼實例跑一下就明白了。

實踐:

接下來咱們用代碼來看一下這個API的效果,先來繪制一個正方形Path:

運行看一下:

好,接下來咱們來截取其中一段,如下:

再次運行:

看到了么,確實如咱們的猜想,該API的作用就是截取Path指定的一個區域,其中它是這樣截的:

注意此時dst的起始點還是中間點:

 

只是由於這個參數為true,為了保證按原圖形的樣子,所以此時變成從這塊畫了:

那如果將“startWithMoveTo”改為false呢?看一下:

而由於為false,則不會保證原圖形了,直接從起始點連到最后一個點了。貌似對於這個“startWithMoveTo”還是有點模糊,下面再來修改一下代碼:

為true則不會變形,還是保持原樣,起始點不會移動。

有沒有發現:

如果 startWithMoveTo 為 true, 則被截取出來到Path片段保持原狀了,而如果 startWithMoveTo 為 false,則會將截取出來的 Path 片段的起始點移動到 dst 的最后一個點,以保證 dst 的連續性。 關於這個參數理解有點生澀,得根據實際你要的效果嘗試一下就知道了,對於之后要操練的這個效果:

很明顯是需要startWithMoveTo 為 true的,因為要維持原狀。

總結:

至此,對於Path的測量基礎就已經學得差不多了,是不是有很多平常都不怎么用到的API深挖一下,感覺在自定義View的場景下還是能發揮一些作用的對吧,下次則就以操練PathMeasure為主,實現不同的效果,加強對於PathMeasure使用的掌握。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM