Android利用 PathMeasure 實現路徑動畫

做者:放碼過來,地址:http://blog.huangyuanlove.com前端

先上圖

咱們能夠利用路徑動畫實現不少好玩的東西,好比上面圖中的相似支付寶支付完成的動畫。主要用到了PathMeasur,ValueAnumator 這兩個類
程序員

PathMeasure

相似於一個計算器,能夠計算一些和路徑相關的東西。兩種初始化方式:web

PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(path,false);

或者編程

PathMeasure pathMeasure = new PathMeasure(path,false);
getLength()

用來獲取路徑長度,而且獲取到的是當前曲線的長度,而不是整個 Path 的長度canvas

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(150150);
    path.addRect(-50,-50,50,50,Path.Direction.CW);
    path.addRect(-100,-100,100,100,Path.Direction.CW);
    path.addRect(-120,-120,120,120,Path.Direction.CW);

    canvas.drawPath(path, paint);
    pathMeasure.setPath(path,false);
    do{
     Log.e("huangyuan",pathMeasure.getLength()+"");
    }while (pathMeasure.nextContour());

}

log以下:api

E/huangyuan: 400.0
E/huangyuan: 800.0
E/huangyuan: 960.0

pathMeasure.nextContour ()獲得的曲線的順序與添加到 Path 中的順序相同數組

getSegment()

函數定義緩存

public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)微信

懶得翻譯,本身看吧app

/**
    * Given a start and stop distance, return in dst the intervening
    * segment(s). If the segment is zero-length, return false, else return
    * true. startD and stopD are pinned to legal values (0..getLength()).
    * If startD >= stopD then return false (and leave dst untouched).
    * Begin the segment with a moveTo if startWithMoveTo is true.
*/

咱們能夠這麼用

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(150150);
    path.addRect(-50,-50,50,50,Path.Direction.CW);
    Path dst = new Path();
    pathMeasure.setPath(path,false);
    pathMeasure.getSegment(0,150,dst,true);
    canvas.drawPath(dst,paint);
}

獲得:

若是 dst 路徑不爲空

Path dst = new Path();
dst.lineTo(10,100);

獲得

若是 dst 路徑不空,startWithMoveTofalse,獲得

路徑加載動畫

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

   private void init() {
       //禁用硬件加速
       setLayerType(LAYER_TYPE_SOFTWARE,null);
       paint = new Paint(Paint.ANTI_ALIAS_FLAG);
       paint.setColor(Color.BLACK);
       paint.setStrokeWidth(4);
       paint.setStyle(Paint.Style.STROKE);
       dstPath = new Path();
       circlePath = new Path();
       circlePath.addCircle(100,100,50,Path.Direction.CW);
       pathMeasure = new PathMeasure(circlePath,true);


       ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);
       valueAnimator.setRepeatCount(ValueAnimator.INFINITE);


       valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
           @Override
           public void onAnimationUpdate(ValueAnimator animation) {
               currentAnimValue = (float) animation.getAnimatedValue();
               invalidate();
           }
       });
       valueAnimator.setDuration(2000);
       valueAnimator.start();

   }


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


       float length = pathMeasure.getLength();

       float stop = pathMeasure.getLength() * currentAnimValue;

       float start = (float) (stop- ((0.5- Math.abs(currentAnimValue-0.5))*length));

       dstPath.reset();
       pathMeasure.getSegment(start,stop,dstPath,true);
       canvas.drawPath(dstPath,paint);
   }

咱們能夠在路徑動畫上加個箭頭,

public class TestPathMeasure extends View {

    private Paint paint;
    private Path path;
    private Path dstPath;
    private Path circlePath;
    private PathMeasure pathMeasure;
    private float currentAnimValue;

    private Bitmap icChevronRight;

    private float[] pos = new float[2];
    private float[] tan = new float[2];


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

    public TestPathMeasure(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

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

    private void init(Context context) {
        //禁用硬件加速
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(4);
        paint.setStyle(Paint.Style.STROKE);
        dstPath = new Path();
        circlePath = new Path();
        circlePath.addCircle(300300150, Path.Direction.CW);
        pathMeasure = new PathMeasure(circlePath, true);
        BitmapFactory.Options options = new BitmapFactory.Options();

        icChevronRight = BitmapFactory.decodeResource(context.getResources(), R.drawable.right);


        ValueAnimator valueAnimator = ValueAnimator.ofFloat(01);
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);


        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentAnimValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.setDuration(2000);
        valueAnimator.start();

    }


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


        float length = pathMeasure.getLength();

        float stop = pathMeasure.getLength() * currentAnimValue;

        float start = (float) (stop - ((0.5 - Math.abs(currentAnimValue - 0.5)) * length));

        dstPath.reset();
        pathMeasure.getSegment(start, stop, dstPath, true);
        canvas.drawPath(dstPath, paint);


        pathMeasure.getPosTan(stop, pos, tan);
        float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180 / Math.PI);

        Matrix matrix = new Matrix();
//        matrix.postRotate(degrees,icChevronRight.getWidth()/2,icChevronRight.getHeight()/2);
//        matrix.postTranslate(pos[0] -icChevronRight.getWidth()/2,pos[1]-icChevronRight.getHeight()/2);

        pathMeasure.getMatrix(stop, matrix, PathMeasure.POSITION_MATRIX_FLAG | PathMeasure.TANGENT_MATRIX_FLAG);
        matrix.preTranslate(-icChevronRight.getWidth() / 2, -icChevronRight.getHeight() / 2);
        canvas.drawBitmap(icChevronRight, matrix, paint);
    }

}

這裏主要用到了 MatrixgetPosTan (), 用來獲得路徑上某一長度的位置以及該位置的正切值。函數原型以下:


/**
 * Pins distance to 0 <= distance <= getLength(), and then computes the
 * corresponding position and tangent. Returns false if there is no path,
 * or a zero-length path was specified, in which case position and tangent
 * are unchanged.
 *
 * @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[])

參數解釋:

  • float distance: 距離 Path 起點的長度,取值範圍 0<=distance<=getLength
  • float [] pos:該點的座標值,pos [0] 表示 x 座標,pos [y] 表示 y 座標
  • float [] tan: 該點的正切值。

在上面的代碼中須要注意的是

  • postan 數組在使用時必須先使用 new 關鍵詞分配存儲空間,而 PathMeasure.getPosTan 函數只會向數組中的元素賦值。
  • 經過 Math.atan2 (tan [1],tan [0]) 獲得的是弧度值,而不是角度
  • 先利用 matrix.postRotate 將圖片旋轉指定角度,而後用 matix.postTranslate將圖片移動到當前路徑最前端 (註釋掉的那兩句)
  • pathMeasure 有個 getMatrix 函數,是對咱們本身實現的那種方式的封裝,咱們只須要將圖片移動一下就行了

山寨支付寶支付成功動畫

就是先畫一個圓,而後圓形裏面畫個對勾。。。。。很簡陋的一種實現方式

public class AliPaySuccess extends View {

    private Paint paint;
    private Path dstPath;
    private Path circlePath;

    private int centerX = 500;
    private int centerY = 500;
    private int radius = 250;
    private PathMeasure pathMeasure;
    private float currentAnimValue;
    private boolean switchLine;

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

    public AliPaySuccess(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

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

    private void init(){
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(4);
        paint.setStyle(Paint.Style.STROKE);
        dstPath = new Path();
        circlePath = new Path();
        circlePath.addCircle(centerX,centerY,radius,Path.Direction.CW);
        circlePath.moveTo(centerX-radius/2,centerY);
        circlePath.lineTo(centerX,centerY+radius/2);
        circlePath.lineTo(centerX+radius/2,centerY-radius/3);
        pathMeasure = new PathMeasure(circlePath,false);
        ValueAnimator animator = ValueAnimator.ofFloat(0,2);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentAnimValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.setDuration(2000);
        animator.setInterpolator(new AccelerateInterpolator());
        animator.start();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(currentAnimValue<1){
            float stop = pathMeasure.getLength()*currentAnimValue;
            pathMeasure.getSegment(0,stop,dstPath,true);
        }else if (currentAnimValue >1 && !switchLine){
            pathMeasure.getSegment(0,pathMeasure.getLength(),dstPath,true);
            switchLine = true;
            pathMeasure.nextContour();
        }else   {
            float stop = pathMeasure.getLength()*(currentAnimValue-1);
            pathMeasure.getSegment(0,stop,dstPath,true);
        }
        canvas.drawPath(dstPath,paint);
    }
}

首先初始化各類參數:畫筆、路徑和動畫

  • 初始化路徑時,先添加了外面的圓形,而後是裏面的對勾。設置動畫從 0~20-1 時畫圓,1-2 時畫對勾。

  • 重寫 onDraw,判斷當前的動畫值,在 0-1時畫圓,當動畫值第一次大於 1 時,切換到對號那條線上。

  • 這裏的 currentAnimValue 不必定會有等於 1 的時候,至少我執行了十幾遍也沒出現一次。因此取的是第一次大於時切換。

這裏面的數值和顏色之類的屬性都是直接寫死的,應該經過 xml 文件讀取。

以上就是本文所有內容,求個三連,奧利給!


---END---


推薦閱讀:
Activity啓動流程詳解(基於api28)
谷歌發佈Android 11 Beta 3 ,距離正式版僅咫尺之遙
TIOBE 8 月編程語言:C、Java 差距拉大,R 語言盛行
Flutter自定義View實戰—仿高德三級聯動Drawer效果!
如何設計一個超牛逼的本地緩存?
Flutter 1.20正式發佈,新特性解讀!
15年程序員經驗分享:40個改變你編程技能的小技巧!
強大!滴滴在 GitHub 開源的項目盤點?
再見,Groovy! 教你如何使用Kotlin SDL 編寫Gradle腳本!
Android 11 的甜點代號曝光!
強大!ASM插樁實現Android端無埋點性能監控!


更文不易,點個「在看」支持一下👇

本文分享自微信公衆號 - 技術最TOP(Tech-Android)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索