Android最全【Android】 给我一个Path,还你一个酷炫动画,华为高级java面试题

重要知识点

下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。

高级进阶篇——高级UI,自定义View(部分展示)

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

  • 面试题部分合集

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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 mPathLengthArray;//路径长度array

private SparseArray 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即可。

最后

感觉现在好多人都在说什么安卓快凉了,工作越来越难找了。又是说什么程序员中年危机啥的,为啥我这年近30的老农根本没有这种感觉,反倒觉得那些贩卖焦虑的都是瞎j8扯谈。当然,职业危机意识确实是要有的,但根本没到那种草木皆兵的地步好吗?

Android凉了都是弱者的借口和说辞。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

所以,最后这里放上我耗时两个月,将自己8年Android开发的知识笔记整理成的Android开发者必知必会系统学习资料笔记,上述知识点在笔记中都有详细的解读,里面还包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。

以上全套学习笔记面试宝典,吃透一半保你可以吊打面试官,只有自己真正强大了,有核心竞争力,你才有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

就是后期我加入扩展的,分析一下这种效果,它和普通的PathAnimView的效果只有动画不同,也不需要额外引入属性暴露出去供设置,所以这种场景,我们只需要重写一个PathAnimHelper类,set给PathAnimView即可。

最后

感觉现在好多人都在说什么安卓快凉了,工作越来越难找了。又是说什么程序员中年危机啥的,为啥我这年近30的老农根本没有这种感觉,反倒觉得那些贩卖焦虑的都是瞎j8扯谈。当然,职业危机意识确实是要有的,但根本没到那种草木皆兵的地步好吗?

Android凉了都是弱者的借口和说辞。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

所以,最后这里放上我耗时两个月,将自己8年Android开发的知识笔记整理成的Android开发者必知必会系统学习资料笔记,上述知识点在笔记中都有详细的解读,里面还包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。

[外链图片转存中…(img-gY0TojG9-1715235459384)]

以上全套学习笔记面试宝典,吃透一半保你可以吊打面试官,只有自己真正强大了,有核心竞争力,你才有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值