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

文章介绍了PathAnimView库,其支持绘制Path的前景和背景色,提供动画设置功能,包括动画时长、无限循环等。还介绍了如何通过SVG转换为Path创建动画,以及高级用法中如何通过自定义PathAnimHelper扩展动画效果。
摘要由CSDN通过智能技术生成

参数

目前可配参数:

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,可以,复杂的还有问题,还需要继续寻找更加方案。

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

}*/

简单用法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了

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

}

}

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

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

我坚信,坚持学习,每天进步一点,滴水穿石,我们离成功都很近!
以下是总结出来的字节经典面试题目,包含:计算机网络,Kotlin,数据结构与算法,Framework源码,微信小程序,NDK音视频开发,计算机网络等。

字节高级Android经典面试题和答案


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-dYNOoPQn-1711909181255)]

[外链图片转存中…(img-egdIOCgy-1711909181255)]

[外链图片转存中…(img-4WMlEjnw-1711909181255)]

[外链图片转存中…(img-bsHONZUQ-1711909181255)]

[外链图片转存中…(img-cYbKJKRM-1711909181256)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

我坚信,坚持学习,每天进步一点,滴水穿石,我们离成功都很近!
以下是总结出来的字节经典面试题目,包含:计算机网络,Kotlin,数据结构与算法,Framework源码,微信小程序,NDK音视频开发,计算机网络等。

字节高级Android经典面试题和答案

[外链图片转存中…(img-DgtnM62u-1711909181256)]
[外链图片转存中…(img-oi83o6eW-1711909181256)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的实现: ```java public class PathAnimationView extends View { private static final int DEFAULT_DURATION = 2000; // 默认动画时长为2秒 private Path mPath; private Paint mPaint; private PathMeasure mPathMeasure; private float mPathLength; private ValueAnimator mValueAnimator; private float mAnimatedValue; private float[] mPos; private float[] mTan; public PathAnimationView(Context context) { this(context, null); } public PathAnimationView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public PathAnimationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setStrokeWidth(5); mPaint.setStyle(Paint.Style.STROKE); mPath = new Path(); mPath.addCircle(0, 0, 100, Path.Direction.CW); mPathMeasure = new PathMeasure(mPath, false); mPathLength = mPathMeasure.getLength(); mPos = new float[2]; mTan = new float[2]; mValueAnimator = ValueAnimator.ofFloat(0, 1); mValueAnimator.setDuration(DEFAULT_DURATION); mValueAnimator.setRepeatCount(ValueAnimator.INFINITE); mValueAnimator.setInterpolator(new LinearInterpolator()); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mAnimatedValue = (float) animation.getAnimatedValue(); invalidate(); } }); } public void startAnimation() { mValueAnimator.start(); } public void stopAnimation() { mValueAnimator.cancel(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.WHITE); float distance = mPathLength * mAnimatedValue; mPathMeasure.getPosTan(distance, mPos, mTan); canvas.drawPath(mPath, mPaint); canvas.drawCircle(mPos[0], mPos[1], 20, mPaint); } } ``` 使用方法: ```java PathAnimationView pathAnimationView = new PathAnimationView(context); addView(pathAnimationView); pathAnimationView.startAnimation(); // 开始动画 pathAnimationView.stopAnimation(); // 停止动画 ``` 这是一个简单的 Path 动画,通过 ValueAnimator 来控制动画进度,再根据 PathMeasure 获取路径上的点,最后绘制圆点实现动画效果。你可以根据具体需求修改 Path,以及动画时长等参数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值