【Android】 给我一个Path,还你一个动画View

转载请标明出处: 
http://blog.csdn.net/zxt0601/article/details/53040506 
本文出自:【张旭童的CSDN】(http://blog.csdn.net/zxt0601
代码传送门:喜欢的话,随手点个star。多谢 
https://github.com/mcxtzhang/PathAnimView

一 概述

原本只是想模仿一下我魂牵梦萦的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"/>123456123456
    Path sPath = new Path();
    sPath.moveTo(0, 0);
    sPath.addCircle(40, 40, 30, Path.Direction.CW);
    pathAnimView1.setSourcePath(sPath);12341234

先看效果图:(真机效果更棒哦,我自己的手机是去年某款599的手机,算是低端的了,6个View一起动画,不会卡,查看GPU呈现模式,95%时间都处于16ms线以下。性能还可以的)

 
其中 
图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);1212

2 动画方面,目前支持设置动画的时长,是否无限循环等。

    //设置了动画总时长,只执行一次的动画
    fillView2.setAnimTime(3000).setAnimInfinite(false).startAnim();1212

3 仿StoreHouse风格的View,还支持设置残影的长度

//设动画时长,设置了stoneHouse残影长度
    storeView3.setPathMaxLength(1200).setAnimTime(20000).startAnim();1212

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

    //当然你可以自己拿到Paint,然后搞事情,我这里设置线条宽度
    pathAnimView1.getPaint().setStrokeWidth(10);1212

数据源:

PathAnimView的数据源是Path。(给我一个Path,还你一个动画View) 
所以内置了几种将别的资源->Path的方法。 
1 直接传string。 StoreHouse风格支持的A-Z,0-9 “.” “- ” ” “(源自百万大神的库文末也有鸣谢,)

    //根据String 转化成Path
    setSourcePath(PathParserUtils.getPathFromArrayFloatList(StoreHousePath.getPath("ZhangXuTong", 1.1f, 16)));1212

2 定义在R.array.xxx里

    //动态设置 从StringArray里取
    storeView2.setSourcePath(PathParserUtils.getPathFromStringArray(this, R.array.storehouse, 3));1212

3 简单的SVG(半成品) 
以前从gayHub上找了一个SVG-PATH的转换类:SvgPathParser,现在派上了用场,简单的SVG-PATH,可以,复杂的还有问题,还需要继续寻找更加方案。 
20170104更新: 
完善的方案已经找到,博文:http://blog.csdn.net/zxt0601/article/details/54018970 
轻松实现图片->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();
        }\*/123456789123456789

简单用法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"/>123456123456

高仿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"/>123456123456

2 开始动画

    fillView1.startAnim();11

3 停止动画

    fillView1.stopAnim();11

4 清除并停止动画

    fillView1.clearAnim();11

稍微 搞基 高级点的用法预览

看到这里细心的朋友可能会发现,上一节,我没有提第5个图View是怎么定义的, 而且第五个View的效果,貌似和其他的不一样,仔细看动画是不是像Android L+的系统自带进度条ProgressBar的效果? 
那说明它的动画效果和我先前提到的两种不一样,是的,一开始我撸是照着StoreHouse那种效果撸的,这是我第二天才扩展的。 
高级的用法,就是本控件动画的扩展性。 
你完全可以通过继承PathAnimHelper类,重写onPathAnimCallback()方法,扩展动画,图5就是这么来的。 
先讲用法预览,稍后章节会详解。 
用法: 
对任意一个普通的PathAnimView,设置一个自定义的PathAnimHelper类即可:

        //代码示例 动态对path加工,通过Helper
        pathAnimView1.setPathAnimHelper(new CstSysLoadAnimHelper(pathAnimView1, pathAnimView1.getSourcePath(), pathAnimView1.getAnimPath()));1212

自定义的PathAnimHelper类:

/\*\*
 \* 介绍:自定义的PathAnimHelper,实现类似Android L+ 进度条效果
 \* 作者:zhangxutong
 \* 邮箱:[email protected]
 \* 时间: 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);
    }
}123456789101112131415161718192021222324252627123456789101112131415161718192021222324252627

伸手党看到这里如果感兴趣,就可以直接一步gayhub了 
(https://github.com/mcxtzhang/PathAnimView
后文比较长,需要自带耐心观看。

二 架构预览

这里我简单画了一下本文介绍的几个类的类图: 
对于重要方法和属性标注了一下。


我们的主角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();
    }
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120

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();
            }123456123456

动画每次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);
    }1234512345

在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();
                    }
                }
            }12345678910111213141234567891011121314

因为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);
    }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133

至此两个最基础的类就讲完了,如此简单就可实现图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);
    }
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666712345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667

2 StoreHouseAnimView

直接上码了,得益于我们的设计,很简单: 
重写getInitAnimHeper() 返回我们的`StoreHouseAn> 转载请标明出处: 

http://blog.csdn.net/zxt0601/article/details/53040506 
本文出自:【张旭童的CSDN】(http://blog.csdn.net/zxt0601
代码传送门:喜欢的话,随手点个star。多谢 
https://github.com/mcxtzhang/PathAnimView

一 概述

原本只是想模仿一下我魂牵梦萦的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"/>123456123456
    Path sPath = new Path();
    sPath.moveTo(0, 0);
    sPath.addCircle(40, 40, 30, Path.Direction.CW);
    pathAnimView1.setSourcePath(sPath);12341234

先看效果图:(真机效果更棒哦,我自己的手机是去年某款599的手机,算是低端的了,6个View一起动画,不会卡,查看GPU呈现模式,95%时间都处于16ms线以下。性能还可以的)

 
其中 
图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);1212

2 动画方面,目前支持设置动画的时长,是否无限循环等。

    //设置了动画总时长,只执行一次的动画
    fillView2.setAnimTime(3000).setAnimInfinite(false).startAnim();1212

3 仿StoreHouse风格的View,还支持设置残影的长度

//设动画时长,设置了stoneHouse残影长度
    storeView3.setPathMaxLength(1200).setAnimTime(20000).startAnim();1212

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

    //当然你可以自己拿到Paint,然后搞事情,我这里设置线条宽度
    pathAnimView1.getPaint().setStrokeWidth(10);1212

数据源:

PathAnimView的数据源是Path。(给我一个Path,还你一个动画View) 
所以内置了几种将别的资源->Path的方法。 
1 直接传string。 StoreHouse风格支持的A-Z,0-9 “.” “- ” ” “(源自百万大神的库文末也有鸣谢,)

    //根据String 转化成Path
    setSourcePath(PathParserUtils.getPathFromArrayFloatList(StoreHousePath.getPath("ZhangXuTong", 1.1f, 16)));1212

2 定义在R.array.xxx里

    //动态设置 从StringArray里取
    storeView2.setSourcePath(PathParserUtils.getPathFromStringArray(this, R.array.storehouse, 3));1212

3 简单的SVG(半成品) 
以前从gayHub上找了一个SVG-PATH的转换类:SvgPathParser,现在派上了用场,简单的SVG-PATH,可以,复杂的还有问题,还需要继续寻找更加方案。 
20170104更新: 
完善的方案已经找到,博文:http://blog.csdn.net/zxt0601/article/details/54018970 
轻松实现图片->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();
        }*/123456789123456789

简单用法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"/>123456123456

高仿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"/>123456123456

2 开始动画

    fillView1.startAnim();11

3 停止动画

    fillView1.stopAnim();11

4 清除并停止动画

    fillView1.clearAnim();11

稍微 搞基 高级点的用法预览

看到这里细心的朋友可能会发现,上一节,我没有提第5个图View是怎么定义的, 而且第五个View的效果,貌似和其他的不一样,仔细看动画是不是像Android L+的系统自带进度条ProgressBar的效果? 
那说明它的动画效果和我先前提到的两种不一样,是的,一开始我撸是照着StoreHouse那种效果撸的,这是我第二天才扩展的。 
高级的用法,就是本控件动画的扩展性。 
你完全可以通过继承PathAnimHelper类,重写onPathAnimCallback()方法,扩展动画,图5就是这么来的。 
先讲用法预览,稍后章节会详解。 
用法: 
对任意一个普通的PathAnimView,设置一个自定义的PathAnimHelper类即可:

        //代码示例 动态对path加工,通过Helper
        pathAnimView1.setPathAnimHelper(new CstSysLoadAnimHelper(pathAnimView1, pathAnimView1.getSourcePath(), pathAnimView1.getAnimPath()));1212

自定义的PathAnimHelper类:

/**
 * 介绍:自定义的PathAnimHelper,实现类似Android L+ 进度条效果
 * 作者:zhangxutong
 * 邮箱:[email protected]
 * 时间: 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);
    }
}
123456789101112131415161718192021222324252627123456789101112131415161718192021222324252627

伸手党看到这里如果感兴趣,就可以直接一步gayhub了 
(https://github.com/mcxtzhang/PathAnimView
后文比较长,需要自带耐心观看。

二 架构预览

这里我简单画了一下本文介绍的几个类的类图: 
对于重要方法和属性标注了一下。


我们的主角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();
    }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120

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();
            }123456123456

动画每次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);
    }
1234512345

在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();
                    }
                }
            }
12345678910111213141234567891011121314

因为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);
    }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133

至此两个最基础的类就讲完了,如此简单就可实现图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);
    }
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666712345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667

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);
    }
}
12345678910111213141516171819202122232425262728293031323334353637381234567891011121314151617181920212223242526272829303132333435363738

五 动态扩展动画效果

前面提过,如图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);
    }1234567891012345678910

六 总结&待完善:

总结起来就是 I have a path.I have a view. (Oh~),Path(Anim)View. 
利用这条裤子,只要传一个Path进去,就可以实现多姿多彩的酷炫Path动画,如果对动画不满意,还可以自己动态扩展

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

20170104更新: 
完善的方案已经找到,博文:http://blog.csdn.net/zxt0601/article/details/54018970 
轻松实现图片->SVG->PATH.

代码传送门:喜欢的话,随手点个star。多谢 
https://github.com/mcxtzhang/PathAnimView

七 引用致谢

StoreHouse风格点阵资源引用处,也是我第一次看见StoreHouse动画:百万大神的库:https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh 
一开始我看到这种动画,我还是个小菜鸡,也不知道怎么实现的,但是一直在我脑海里挥之不去。 
后来突然有一天想到可以用Path、PathMeasure做自定义View动画来实现,就开始动笔写了起来。 
发现Path的路径不太好获取,于是翻看百万大神的库,发现他并不是使用的Path动画,但是的的确确是利用点阵设置数据源的,于是我就借助了这些最原始的点阵资源,撸出了这么一个Path动画。

最初只是想实现这么一个效果,了却我的心愿,没想到还有意外收获。有了这个Path动画工具类库。 
说实话写这么一个东西,我不知不觉也提升了,以前可能不太会把层级分的这么开,利用继承组合的方式去扩展功能。 
以前大多还是C V 一份代码改一改,像图上的效果,我可能会分开自定义三个View去做,复制一些重复代码也不在乎,看来坚持会有收获。 
希望我们都一起进步。

===imHelper`,并增加残影长度的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);
    }
}12345678910111213141516171819202122232425262728293031323334353637381234567891011121314151617181920212223242526272829303132333435363738

五 动态扩展动画效果

前面提过,如图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);
    }1234567891012345678910

六 总结&待完善:

总结起来就是 I have a path.I have a view. (Oh~),Path(Anim)View. 
利用这条裤子,只要传一个Path进去,就可以实现多姿多彩的酷炫Path动画,如果对动画不满意,还可以自己动态扩展

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

20170104更新: 
完善的方案已经找到,博文:http://blog.csdn.net/zxt0601/article/details/54018970 
轻松实现图片->SVG->PATH.

代码传送门:喜欢的话,随手点个star。多谢 
https://github.com/mcxtzhang/PathAnimView

七 引用致谢

StoreHouse风格点阵资源引用处,也是我第一次看见StoreHouse动画:百万大神的库:https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh 
一开始我看到这种动画,我还是个小菜鸡,也不知道怎么实现的,但是一直在我脑海里挥之不去。 
后来突然有一天想到可以用Path、PathMeasure做自定义View动画来实现,就开始动笔写了起来。 
发现Path的路径不太好获取,于是翻看百万大神的库,发现他并不是使用的Path动画,但是的的确确是利用点阵设置数据源的,于是我就借助了这些最原始的点阵资源,撸出了这么一个Path动画。

最初只是想实现这么一个效果,了却我的心愿,没想到还有意外收获。有了这个Path动画工具类库。 
说实话写这么一个东西,我不知不觉也提升了,以前可能不太会把层级分的这么开,利用继承组合的方式去扩展功能。 
以前大多还是C V 一份代码改一改,像图上的效果,我可能会分开自定义三个View去做,复制一些重复代码也不在乎,看来坚持会有收获。 
希望我们都一起进步。

===