【Android】 給我一個Path,還你一個動畫View

mcxtzhang發表於2019-02-25

本篇文章已授權微信公眾號 hongyangAndroid (鴻洋)獨家釋出

轉載請標明出處:
gold.xitu.io/post/581c51…
本文出自:【張旭童的稀土掘金】(gold.xitu.io/user/56de21…)
程式碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/P…

一 概述

原本只是想模仿一下我魂牽夢縈的StoreHouse效果,沒想到意外擼出來一個工具庫。

最簡單用法,給我一個path(可以有多段),我還你一個動畫。

I have a path.I have a view. (Oh~),Path(Anim)View.

    <com.mcxtzhang.pathanimlib.PathAnimView
        android:id="@+id/pathAnimView1"
        android:layout_width="wrap_content"
        android:layout_height="60dp"
        android:background="@color/blue"
        android:padding="5dp"/>複製程式碼
    Path sPath = new Path();
    sPath.moveTo(0, 0);
    sPath.addCircle(40, 40, 30, Path.Direction.CW);
    pathAnimView1.setSourcePath(sPath);複製程式碼

先看效果圖:(真機效果更棒哦,我自己的手機是去年某款599的手機,算是低端的了,6個View一起動畫,不會卡,檢視GPU呈現模式,95%時間都處於16ms線以下。效能還可以的)

【Android】 給我一個Path,還你一個動畫View

其中
圖1 是普通逐漸填充的效果,無限迴圈。
圖2 是仿StoreHouse 殘影流動效果。(但與原版並不是完全一模一樣,估計原版不是用Path做的)
圖3 是逐漸填充的效果,設定了只執行一次。
圖4 是仿StoreHouse效果。資料來源來自R.array.xxxx
圖5 是另一種自定義PathAnimHelper實現的自定義動畫效果。類似Android L+ 系統進度條效果。
圖6 是仿StoreHouse效果,但是將動畫時長設定的很大,所以能看到它逐漸的過程。

引數

目前可配引數:
1 繪製方面,支援繪製Path的前景 背景色。

    //設定顏色
    fillView2.setColorBg(Color.WHITE).setColorFg(Color.BLACK);複製程式碼

2 動畫方面,目前支援設定動畫的時長,是否無限迴圈等。

    //設定了動畫總時長,只執行一次的動畫
    fillView2.setAnimTime(3000).setAnimInfinite(false).startAnim();複製程式碼

3 仿StoreHouse風格的View,還支援設定殘影的長度

//設動畫時長,設定了stoneHouse殘影長度
    storeView3.setPathMaxLength(1200).setAnimTime(20000).startAnim();複製程式碼

4 當然你可以拿到Paint自己搞事情:

    //當然你可以自己拿到Paint,然後搞事情,我這裡設定線條寬度
    pathAnimView1.getPaint().setStrokeWidth(10);複製程式碼

資料來源:

PathAnimView的資料來源是Path。(給我一個Path,還你一個動畫View)
所以內建了幾種將別的資源->Path的方法。
1 直接傳string。 StoreHouse風格支援的A-Z,0-9 "." "- " " "(源自百萬大神的庫文末也有鳴謝,)

    //根據String 轉化成Path
    setSourcePath(PathParserUtils.getPathFromArrayFloatList(StoreHousePath.getPath("ZhangXuTong", 1.1f, 16)));複製程式碼

2 定義在R.array.xxx裡

    //動態設定 從StringArray裡取
    storeView2.setSourcePath(PathParserUtils.getPathFromStringArray(this, R.array.storehouse, 3));複製程式碼

3 簡單的SVG(半成品)
以前從gayHub上找了一個SVG-PATH的轉換類:SvgPathParser,現在派上了用場,簡單的SVG-PATH,可以,複雜的還有問題,還需要繼續尋找更加方案。

        //SVG轉-》path
        //還在完善中,我從github上找了如下工具類,發現簡單的SVG可以轉path,複雜點的 就亂了
/*        SvgPathParser svgPathParser = new SvgPathParser();
        try {
            Path path = svgPathParser.parsePath("M1,1 L1,50 L50,50 L50,50 L50,1 Z");
            storeView3.setSourcePath(path);
        } catch (ParseException e) {
            e.printStackTrace();
        }*/複製程式碼

簡單用法API

1 xml定義

普通PathAnimView
效果如圖1 3。動畫是 進度填充直到滿的效果。

    <com.mcxtzhang.pathanimlib.PathAnimView
        android:id="@+id/pathAnimView1"
        android:layout_width="wrap_content"
        android:layout_height="60dp"
        android:background="@color/blue"
        android:padding="5dp"/>複製程式碼

高仿StoreHouse風格AnimView:
這種View顯示出來的效果如圖2 4 6 。動畫是 殘影流動的效果。

    <com.mcxtzhang.pathanimlib.StoreHouseAnimView
        android:id="@+id/storeView3"
        android:layout_width="wrap_content"
        android:layout_height="60dp"
        android:background="@android:color/black"
        android:padding="5dp"/>複製程式碼

2 開始動畫

    fillView1.startAnim();複製程式碼

3 停止動畫

    fillView1.stopAnim();複製程式碼

4 清除並停止動畫

    fillView1.clearAnim();複製程式碼

稍微 搞基 高階點的用法預覽

看到這裡細心的朋友可能會發現,上一節,我沒有提第5個圖View是怎麼定義的, 而且第五個View的效果,貌似和其他的不一樣,仔細看動畫是不是像Android L+的系統自帶進度條ProgressBar的效果?
那說明它的動畫效果和我先前提到的兩種不一樣,是的,一開始我擼是照著StoreHouse那種效果擼的,這是我第二天才擴充套件的。
高階的用法,就是本控制元件動畫的擴充套件性
你完全可以通過繼承PathAnimHelper類,重寫onPathAnimCallback()方法,擴充套件動畫,圖5就是這麼來的。
先講用法預覽,稍後章節會詳解。
用法
對任意一個普通的PathAnimView,設定一個自定義的PathAnimHelper類即可:

        //程式碼示例 動態對path加工,通過Helper
        pathAnimView1.setPathAnimHelper(new CstSysLoadAnimHelper(pathAnimView1, pathAnimView1.getSourcePath(), pathAnimView1.getAnimPath()));複製程式碼

自定義的PathAnimHelper類:

/**
 * 介紹:自定義的PathAnimHelper,實現類似Android L+ 進度條效果
 * 作者:zhangxutong
 * 郵箱:zhangxutong@imcoming.com
 * 時間: 2016/11/3.
 */

public class CstSysLoadAnimHelper extends PathAnimHelper {
    public CstSysLoadAnimHelper(View view, Path sourcePath, Path animPath) {
        super(view, sourcePath, animPath);
    }

    public CstSysLoadAnimHelper(View view, Path sourcePath, Path animPath, long animTime, boolean isInfinite) {
        super(view, sourcePath, animPath, animTime, isInfinite);
    }

    @Override
    public void onPathAnimCallback(View view, Path sourcePath, Path animPath, PathMeasure pathMeasure, ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();
        //獲取一個段落
        float end = pathMeasure.getLength() * value;
        float begin = (float) (end - ((0.5 - Math.abs(value - 0.5)) * pathMeasure.getLength()));
        animPath.reset();
        animPath.lineTo(0, 0);
        pathMeasure.getSegment(begin, end, animPath, true);
    }
}複製程式碼

伸手黨看到這裡如果感興趣,就可以直接一步gayhub了
(github.com/mcxtzhang/P…)
後文比較長,需要自帶耐心觀看。

二 架構預覽

這裡我簡單畫了一下本文介紹的幾個類的類圖:
對於重要方法和屬性標註了一下。

【Android】 給我一個Path,還你一個動畫View

我們的主角PathAnimView繼承自View,是一個自定義View。
它內部持有一個PathAnimHelper,專注做Path動畫。它預設的實現是 逐漸填充 的動畫效果。

一般情況下只需要更換PathAnimHelper,PathAnimView即可做出不同的動畫。(圖1第5個View)

但是如果需要擴充一些動畫屬性供使用者設定,例如仿StoreHouse風格的動畫View,想暴露 殘影長度 屬性供設定。

我這裡採用的是:繼承自PathAnimView,並增加屬性get、set 方法,並重寫getInitAnimHeper()方法,返回自定義的PathAnimHelper

StoreHouseAnimView繼承自PathAnimView,增加了殘影長度的get、set方法。並重寫getInitAnimHeper()方法,返回StoreHouseAnimHelper物件。 StoreHouseAnimHelper類繼承的是PathAnimHelper

三 基礎類的實現:

基礎類是PathAnimViewPathAnimHelper

1 PathAnimView

先看PathAnimView:
這裡我將一些不重要的get、set方法和構造方法剔除,留下比較重要的方法。

一個做路徑動畫的View

  • 利用源Path繪製“底”
  • 利用動畫Path 繪製 填充動畫
  • 通過mPathAnimHelper 對SourcePath做動畫:
  • 一個SourcePath 內含多段Path,迴圈取出每段Path,並做一個動畫

程式碼本身不難,註釋也比較詳細,核心的話,就是onDraw()方法咯:
我這裡用平移做的paddingLeft、paddingTop。
先利用源Path(mSourcePath)繪製底邊的樣子。
再利用變化的animPath(mAnimPath)繪製前景,這樣animPath不斷變化,並且重繪View->onDraw(),前景就會不斷變化,形成動畫效果。
那麼核心就是animPath的的變化了,animPath的變化交由 mPathAnimHelper去做。


核心原始碼如下:

public class PathAnimView extends View {
    protected Path mSourcePath;//需要做動畫的源Path
    protected Path mAnimPath;//用於繪製動畫的Path
    protected Paint mPaint;
    protected int mColorBg = Color.GRAY;//背景色
    protected int mColorFg = Color.WHITE;//前景色 填充色
    protected PathAnimHelper mPathAnimHelper;//Path動畫工具類

    protected int mPaddingLeft, mPaddingTop;

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

    /**
     * 這個方法可能會經常用到,用於設定源Path
     *
     * @param sourcePath
     * @return
     */
    public PathAnimView setSourcePath(Path sourcePath) {
        mSourcePath = sourcePath;
        initAnimHelper();
        return this;
    }

    /**
     * INIT FUNC
     **/
    protected void init() {
        //Paint
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);

        //動畫路徑只要初始化即可
        mAnimPath = new Path();

        //初始化動畫幫助類
        initAnimHelper();

    }

    /**
     * 初始化動畫幫助類
     */
    protected void initAnimHelper() {
        mPathAnimHelper = getInitAnimHeper();
        //mPathAnimHelper = new PathAnimHelper(this, mSourcePath, mAnimPath, 1500, true);
    }

    /**
     * 子類可通過重寫這個方法,返回自定義的AnimHelper
     *
     * @return
     */
    protected PathAnimHelper getInitAnimHeper() {
        return new PathAnimHelper(this, mSourcePath, mAnimPath);
    }

    /**
     * draw FUNC
     **/
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //平移
        canvas.translate(mPaddingLeft, mPaddingTop);

        //先繪製底,
        mPaint.setColor(mColorBg);
        canvas.drawPath(mSourcePath, mPaint);

        //再繪製前景,mAnimPath不斷變化,不斷重繪View的話,就會有動畫效果。
        mPaint.setColor(mColorFg);
        canvas.drawPath(mAnimPath, mPaint);
    }

    /**
     * 設定動畫 迴圈
     */
    public PathAnimView setAnimInfinite(boolean infinite) {
        mPathAnimHelper.setInfinite(infinite);
        return this;
    }

    /**
     * 設定動畫 總時長
     */
    public PathAnimView setAnimTime(long animTime) {
        mPathAnimHelper.setAnimTime(animTime);
        return this;
    }

    /**
     * 執行迴圈動畫
     */
    public void startAnim() {
        mPathAnimHelper.startAnim();
    }

    /**
     * 停止動畫
     */
    public void stopAnim() {
        mPathAnimHelper.stopAnim();
    }

    /**
     * 清除並停止動畫
     */
    public void clearAnim() {
        stopAnim();
        mAnimPath.reset();
        mAnimPath.lineTo(0, 0);
        invalidate();
    }
}複製程式碼

2 PathAnimHelper

看看最基礎的PathAnimHelper類是怎麼做的,一樣省略一些程式碼:
它是一個PathAnimView的Path動畫的工具類

  • 一個SourcePath 內含多段(一段)Path,迴圈取出每段Path,並做一個動畫,
  • 預設動畫時間1500ms,無限迴圈
  • 可以通過建構函式修改這兩個引數
  • 對外暴露 startAnim() 和 stopAnim()兩個方法
  • 子類可通過重寫onPathAnimCallback()方法,對animPath進行再次操作,從而定義不同的動畫效果

值得一提的是,這裡的動畫時間,是指迴圈取出SourcePath裡的N段Path的總時間。


startAnim()方法是入口,這個方法會在PathAnimView裡被呼叫。
startAnim()方法裡,先初始化一個PathMeasure,以及重置animPath
然後利用PathMeasure.nextContour()方法,迴圈一遍SourcePath的Path段數count,
利用這個count求出每段小Path應該執行的動畫時間:totalDuaration / count
然後便呼叫loopAnim()方法,迴圈取出每一段path ,並執行動畫。


loopAnim()方法裡,定義一個無限迴圈的屬性動畫mAnimator
為其設定AnimatorUpdateListeneronAnimationRepeat,監聽動畫的更新和重複。
重點就在這兩個監聽器裡:

            public void onAnimationUpdate(ValueAnimator animation) {
                //增加一個callback 便於子類重寫搞事情
                onPathAnimCallback(view, sourcePath, animPath, pathMeasure, animation);
                //通知View重新整理自己
                view.invalidate();
            }複製程式碼

動畫每次Update的時候,回撥onPathAnimCallback()方法,在裡面對animPath做處理。
對AnimPath處理以後,就可以讓View繪製新animPath形成動畫了:
然後就是讓View重繪,這樣就會重走onDraw()方法,就是上一節提到的內容。


onPathAnimCallback()方法也很簡單,按動畫進度值,取出當前這一小段的path的部分路徑,賦值給animPath。

    public void onPathAnimCallback(View view, Path sourcePath, Path animPath, PathMeasure pathMeasure, ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();
        //獲取一個段落
        pathMeasure.getSegment(0, pathMeasure.getLength() * value, animPath, true);
    }複製程式碼

在Repeat監聽器裡:

            public void onAnimationRepeat(Animator animation) {
                //繪製完一條Path之後,再繪製下一條
                pathMeasure.nextContour();
                //長度為0 說明一次迴圈結束
                if (pathMeasure.getLength() == 0) {
                    if (isInfinite) {//如果需要迴圈動畫
                        animPath.reset();
                        animPath.lineTo(0, 0);
                        pathMeasure.setPath(sourcePath, false);
                    } else {//不需要就停止(因為repeat是無限 需要手動停止)
                        animation.end();
                    }
                }
            }複製程式碼

因為SourcePath裡是可能含有1+段Path的,這裡是合適的時機,利用pathMeasure.nextContour();迴圈取出下一段Path, 判斷一下新Path的長度,如果為0,說明這一次大迴圈結束,即使用者視覺上的一次動畫進度100%了。
這裡判斷我們設定的isInfinite屬性,
如果是true,說明是迴圈動畫,那麼做初始化工作:
清空我們的animPath,初始化pathMeasure。(和startAnim()方法裡的初始化工作一致)。
如果是false,說明動畫需要停止,那麼手動呼叫animation.end()停止動畫。(圖1,第三個動畫)


核心原始碼如下:

public class PathAnimHelper {
    protected static final long mDefaultAnimTime = 1500;//預設動畫總時間

    protected View mView;//執行動畫的View
    protected Path mSourcePath;//源Path
    protected Path mAnimPath;//用於繪製動畫的Path
    protected long mAnimTime;//動畫一共的時間
    protected boolean mIsInfinite;//是否無限迴圈

    protected ValueAnimator mAnimator;//動畫物件

    public PathAnimHelper(View view, Path sourcePath, Path animPath, long animTime, boolean isInfinite) {
        if (view == null || sourcePath == null || animPath == null) {
            Log.e(TAG, "PathAnimHelper init error: view 、sourcePath、animPath can not be null");
            return;
        }
        mView = view;
        mSourcePath = sourcePath;
        mAnimPath = animPath;
        mAnimTime = animTime;
        mIsInfinite = isInfinite;
    }

    /**
     * 執行動畫
     */
    public void startAnim() {
        startAnim(mView, mSourcePath, mAnimPath, mAnimTime, mIsInfinite);
    }

    /**
     * 一個SourcePath 內含多段Path,迴圈取出每段Path,並做一個動畫
     * 自定義動畫的總時間
     * 和是否迴圈
     *
     * @param view           需要做動畫的自定義View
     * @param sourcePath     源Path
     * @param animPath       自定義View用這個Path做動畫
     * @param totalDuaration 動畫一共的時間
     * @param isInfinite     是否無限迴圈
     */
    protected void startAnim(View view, Path sourcePath, Path animPath, long totalDuaration, boolean isInfinite) {
        if (view == null || sourcePath == null || animPath == null) {
            return;
        }
        PathMeasure pathMeasure = new PathMeasure();
        //先重置一下需要顯示動畫的path
        animPath.reset();
        animPath.lineTo(0, 0);
        pathMeasure.setPath(sourcePath, false);
        //這裡僅僅是為了 計算一下每一段的duration
        int count = 0;
        while (pathMeasure.getLength() != 0) {
            pathMeasure.nextContour();
            count++;
        }
        //經過上面這段計算duration程式碼的折騰 需要重新初始化pathMeasure
        pathMeasure.setPath(sourcePath, false);
        loopAnim(view, sourcePath, animPath, totalDuaration, pathMeasure, totalDuaration / count, isInfinite);
    }


    /**
     * 迴圈取出每一段path ,並執行動畫
     *
     * @param animPath    自定義View用這個Path做動畫
     * @param pathMeasure 用於測量的PathMeasure
     */
    protected void loopAnim(final View view, final Path sourcePath, final Path animPath, final long totalDuaration, final PathMeasure pathMeasure, final long duration, final boolean isInfinite) {
        //動畫正在執行的話,先stop吧。萬一有人要使用新動畫呢,(正經使用者不會這麼用。)
        stopAnim();

        mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
        mAnimator.setInterpolator(new LinearInterpolator());
        mAnimator.setDuration(duration);
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //增加一個callback 便於子類重寫搞事情
                onPathAnimCallback(view, sourcePath, animPath, pathMeasure, animation);
                //通知View重新整理自己
                view.invalidate();
            }
        });

        mAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationRepeat(Animator animation) {
                //每段path走完後,要補一下 某些情況會出現 animPath不滿的情況
                pathMeasure.getSegment(0, pathMeasure.getLength(), animPath, true);

                //繪製完一條Path之後,再繪製下一條
                pathMeasure.nextContour();
                //長度為0 說明一次迴圈結束
                if (pathMeasure.getLength() == 0) {
                    if (isInfinite) {//如果需要迴圈動畫
                        animPath.reset();
                        animPath.lineTo(0, 0);
                        pathMeasure.setPath(sourcePath, false);
                    } else {//不需要就停止(因為repeat是無限 需要手動停止)
                        animation.end();
                    }
                }
            }
        });
        mAnimator.start();
    }

    /**
     * 停止動畫
     */
    public void stopAnim() {
        if (null != mAnimator && mAnimator.isRunning()) {
            mAnimator.end();
        }
    }

    /**
     * 用於子類繼承搞事情,對animPath進行再次操作的函式
     *
     * @param view
     * @param sourcePath
     * @param animPath
     * @param pathMeasure
     */
    public void onPathAnimCallback(View view, Path sourcePath, Path animPath, PathMeasure pathMeasure, ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();
        //獲取一個段落
        pathMeasure.getSegment(0, pathMeasure.getLength() * value, animPath, true);
    }
}複製程式碼

至此兩個最基礎的類就講完了,如此簡單就可實現圖1第1、3個動畫效果。

四 實現StoreHouse風格

我們前面提過,擴充套件動畫,核心是繼承PathAnimHelper 重寫onPathAnimCallback()方法即可,所以實現StoreHouse風格,核心類就是StoreHouseAnimHelper

1 StoreHouseAnimHelper

  • 介紹:仿StoreHouse風格的PathAnimHepler
  • 增加了一個動畫殘影長度的屬性:mPathMaxLength,預設值是400
  • 因沒有找到有用的API,這裡實現StoreHouse的方法,是手工計算的,不是很爽。
  • 思路是是迴圈一遍AnimPath,記錄裡面每一段小Path的length。
  • 然後再逆序遍歷AnimPath,從後面擷取 殘影長度 的Path,
  • 再複製給AnimPath。

核心程式碼如下:

public class StoreHouseAnimHelper extends PathAnimHelper {
    private final static long MAX_LENGTH = 400;

    private long mPathMaxLength;//殘影路徑最大長度
    Path mStonePath;//暫存一下路徑,最終要複製給animPath的
    PathMeasure mPm;
    private ArrayList<Float> mPathLengthArray;//路徑長度array
    private SparseArray<Boolean> mPathNeedAddArray;//路徑是否需要被全部Add的Array
    private int partIndex;//殘缺的index
    private float partLength;//殘缺部分的長度

    public StoreHouseAnimHelper(View view, Path sourcePath, Path animPath, long animTime, boolean isInfinite) {
        super(view, sourcePath, animPath, animTime, isInfinite);
        mPathMaxLength = MAX_LENGTH;
        mStonePath = new Path();
        mPm = new PathMeasure();
        mPathLengthArray = new ArrayList<>();//順序存放path的length
        mPathNeedAddArray = new SparseArray<>();
    }

    @Override
    public void onPathAnimCallback(View view, Path sourcePath, Path animPath, PathMeasure pathMeasure, ValueAnimator animation) {
        super.onPathAnimCallback(view, sourcePath, animPath, pathMeasure, animation);
        //仿StoneHouse效果 ,現在的做法很挫
        //重置變數
        mStonePath.reset();
        mStonePath.lineTo(0, 0);
        mPathLengthArray.clear();

        //迴圈一遍AnimPath,記錄裡面每一段小Path的length。
        mPm.setPath(animPath, false);
        while (mPm.getLength() != 0) {
            mPathLengthArray.add(mPm.getLength());
            mPm.nextContour();
        }

        //逆序遍歷AnimPath,記錄哪些子Path是需要add的,並且記錄那段需要部分add的path的下標
        mPathNeedAddArray.clear();
        float totalLength = 0;
        partIndex = 0;
        partLength = 0;
        for (int i = mPathLengthArray.size() - 1; i >= 0; i--) {
            if (totalLength + mPathLengthArray.get(i) <= mPathMaxLength) {//加上了也沒滿
                mPathNeedAddArray.put(i, true);
                totalLength = totalLength + mPathLengthArray.get(i);
            } else if (totalLength < mPathMaxLength) {//加上了滿了,但是不加就沒滿
                partIndex = i;
                partLength = mPathMaxLength - totalLength;
                totalLength = totalLength + mPathLengthArray.get(i);
            }
        }
        //迴圈Path,並得到最終要顯示的AnimPath
        mPm.setPath(animPath, false);
        int i = 0;
        while (mPm.getLength() != 0) {
            if (mPathNeedAddArray.get(i, false)) {
                mPm.getSegment(0, mPm.getLength(), mStonePath, true);
            } else if (i == partIndex) {
                mPm.getSegment(mPm.getLength() - partLength, mPm.getLength(), mStonePath, true);
            }
            mPm.nextContour();
            i++;
        }

        animPath.set(mStonePath);
    }
}複製程式碼

2 StoreHouseAnimView

直接上碼了,得益於我們的設計,很簡單:
重寫getInitAnimHeper() 返回我們的StoreHouseAnimHelper,並增加殘影長度的get、set方法。

public class StoreHouseAnimView extends PathAnimView {

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

    public StoreHouseAnimView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    /**
     * GET SET FUNC
     **/
    public long getPathMaxLength() {
        return ((StoreHouseAnimHelper) mPathAnimHelper).getPathMaxLength();
    }

    /**
     * 設定殘影最大長度
     *
     * @param pathMaxLength
     * @return
     */
    public StoreHouseAnimView setPathMaxLength(long pathMaxLength) {
        ((StoreHouseAnimHelper) mPathAnimHelper).setPathMaxLength(pathMaxLength);
        return this;
    }

    @Override
    protected PathAnimHelper getInitAnimHeper() {
        return new StoreHouseAnimHelper(this, mSourcePath, mAnimPath);
    }
}複製程式碼

五 動態擴充套件動畫效果

前面提過,如圖1第五個動畫的效果,就是後期我加入擴充套件的,分析一下這種效果,它和普通的PathAnimView的效果只有動畫不同,也不需要額外引入屬性暴露出去供設定,所以這種場景,我們只需要重寫一個PathAnimHelper類,set給PathAnimView即可。
程式碼第一章節也提過,


一點注意的地方就是,這裡沒有同第四章節那樣呼叫super.onPathAnimCallback(view, sourcePath, animPath, pathMeasure, animation);
因為第四章仿StoreHouse的效果,是在第三章的效果基礎之上加工而成的。所以需要PathAnimHeper先處理一下。

而我們這裡實現的仿系統ProgressBar的效果,則是完全重寫的。
核心方法如下重寫,很簡單,不再贅述:

    @Override
    public void onPathAnimCallback(View view, Path sourcePath, Path animPath, PathMeasure pathMeasure, ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();
        //獲取一個段落
        float end = pathMeasure.getLength() * value;
        float begin = (float) (end - ((0.5 - Math.abs(value - 0.5)) * pathMeasure.getLength()));
        animPath.reset();
        animPath.lineTo(0, 0);
        pathMeasure.getSegment(begin, end, animPath, true);
    }複製程式碼

六 總結&待完善:

總結起來就是 I have a path.I have a view. (Oh~),Path(Anim)View.
利用這條褲子,只要傳一個Path進去,就可以實現多姿多彩的酷炫Path動畫,如果對動畫不滿意,還可以自己動態擴充套件

目前最急需完善的:
SVG->Android PATH的轉換,
希望有知道的兄弟可以告知一下,多謝。

程式碼傳送門:喜歡的話,隨手點個star。多謝
github.com/mcxtzhang/P…

七 引用致謝

StoreHouse風格點陣資源引用處,也是我第一次看見StoreHouse動畫:百萬大神的庫: github.com/liaohuqiu/a…
一開始我看到這種動畫,我還是個小菜雞,也不知道怎麼實現的,但是一直在我腦海裡揮之不去。
後來突然有一天想到可以用Path、PathMeasure做自定義View動畫來實現,就開始動筆寫了起來。
發現Path的路徑不太好獲取,於是翻看百萬大神的庫,發現他並不是使用的Path動畫,但是的的確確是利用點陣設定資料來源的,於是我就藉助了這些最原始的點陣資源,擼出了這麼一個Path動畫。

最初只是想實現這麼一個效果,了卻我的心願,沒想到還有意外收穫。有了這個Path動畫工具類庫。
說實話寫這麼一個東西,我不知不覺也提升了,以前可能不太會把層級分的這麼開,利用繼承組合的方式去擴充套件功能。
以前大多還是C V 一份程式碼改一改,像圖上的效果,我可能會分開自定義三個View去做,複製一些重複程式碼也不在乎,看來堅持會有收穫。
希望我們都一起進步。

相關文章