
Step 1. 在项目根 build.gradle 文件中增加 JitPack 仓库依赖。
allprojects { repositories { ... maven { url "https://jitpack.io" } } }
Step 2. Add the dependency
dependencies { compile 'com.github.mcxtzhang:PathAnimView:V1.0.0' }
相关博文: http://gold.xitu.io/post/581c516a0ce46300587a10db
一 概述原本只是想模仿一下我魂牵梦萦的 StoreHouse 效果,没想到意外撸出来一个工具库。
最简单用法,给我一个 path,我还你一个动画。
I have a path.I have a view. (Oh~),Path(Anim)View.
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 线以下。性能还可以的)
其中 图 1 是普通逐渐填充的效果,无限循环。 图 2 是仿 StoreHouse 残影流动效果。(但与原版并不是完全一模一样,估计原版不是用 Path 做的) 图 3 是逐渐填充的效果,设置了只执行一次。 图 4 是仿 StoreHouse 效果。数据源来自 R.array.xxxx 图 5 是另一种自定义 PathAnimHelper 实现的自定义动画效果。类似 Android L+ 系统进度条效果。 图 6 是仿 StoreHouse 效果,但是将动画时长设置的很大,所以能看到它逐渐的过程。
StoneHouse 效果如下简单使用:
pathAnimView1 = (StoreHouseAnimView) findViewById(R.id.pathAnimView1); Path sPath = new Path(); sPath.moveTo(0, 0); sPath.addCircle(40, 40, 30, Path.Direction.CW); pathAnimView1.setSourcePath(PathParserUtils.getPathFromArrayFloatList(StoreHousePath.getPath("McXtZhang"))); pathAnimView1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { pathAnimView1.startAnim(); } });参数
目前可配参数: 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。动画是 进度填充直到满的效果。
高仿 StoreHouse 风格 AnimView: 这种 View 显示出来的效果如图 2 4 6 。动画是 残影流动的效果。
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); } }二 逐个介绍
这里我简单画了一下本文介绍的几个类的类图: 对于重要方法和属性标注了一下。
我们的主角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。
先看PathAnimView: 这里我将一些不重要的 get、set 方法和构造方法剔除,留下比较重要的方法。
/** * 介绍:一个路径动画的 View * 利用源 Path 绘制“底” * 利用动画 Path 绘制 填充动画 ** 一个 SourcePath 内含多段 Path,循环取出每段 Path,并做一个动画, *
* 作者:zhangxutong * 邮箱:zhangxutong@imcoming.com * 时间: 2016/11/2. */ 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); 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(); } }
代码本身不难,注释也比较详细,核心的话,就是onDraw()方法咯: 我这里用平移做的 paddingLeft、paddingTop。 先利用源 Path 绘制底边的样子。 再利用 animPath 绘制前景,这样 animPath 不断变化,并且重绘 View->onDraw(),前景就会不断变化,形成动画效果。 那么核心就是 animPath 的的变化了,animPath 的变化交由 mPathAnimHelper 去做。
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); }