我们借上篇继续

直切主题:

当然有兴趣的可以加入Android工程师交流QQ群:752016839 主要针对Android开发人员提升自己,突破瓶颈,相信你来学习,会有提升和收获。在这个群里会有你所需要的内容,

列表展开闭合

关于这个点貌似挺尴尬的,因为用MTRVA可以轻松实现,我都不知道要不要介绍,关键有几个用户同学会问这个,趁这个机会简单说一下吧。

比如效果图中到联系人列表有展开闭合的效果,怎么实现的呢?首先我们要排除直接操作view,比如让view隐藏等等。这是不可取的,应该用数据去驱动UI,这个我已经强调很多遍了。先看一看代码:

private void switchStatus(ContactHeader item, View icon) {final int index = mData.indexOf(item);final String flag = item.getIndex();if (item.isFlod()) {icon.animate().rotation(90).setDuration(500).start();mHelper.addData(index + 1, item.getChilds());} else {icon.animate().rotation(-90).setDuration(500).start();...childs = mHelper.removeData(index + 1, count);item.setChilds(childs);}item.switchStatus();mHelper.setData(index, item);
}
复制代码

很简单的逻辑,如果当前是闭合状态,icon需要选择90度且添加数据,反之,旋转-90度并移除数据。

关键点就在于addData和removeData,底层调用的是adapter的notifyItemRangeInserted和notifyItemRangeRemoved方法,因此是附带动画的,且对数组越界做了优化,例如addData的position大于或等于集合数量,那么直接将应添加的数据直接添加在集合的末尾,而不是抛异常。

本节主要希望大家利用添加和移除数据来达到展开闭合的效果。

转场动画进阶

每次玩Material Design产品的时候,总是有很多炫酷的转场动画刺激我。相信也有很多同学跟我一样,很喜欢这种风格。今天我就带大家了解一下转场动画并自定义转场动画,走起!

常见转场动画

  • makeClipRevealAnimation (View source, int startX, int startY, int width, int height):从一个原点以圆形扩展至满屏,需要传入新Activity动画开始的view,因为其实坐标(startX, startY)都是相对source的,width和height是新Activity的初始宽高
  • makeCustomAnimation (Context context, int enterResId, int exitResId):这个类似我们的overridePendingTransition,传入进退动画即可
  • makeScaleUpAnimation (View source, int startX, int startY, int startWidth, int startHeight) :与makeClipRevealAnimation相似,文档主要区别是setSourceBounds的设置
  • makeThumbnailScaleUpAnimation (View source, Bitmap thumbnail, int startX, int startY):指定一张缩略图缩放转场,参数与makeClipRevealAnimation意义相同
  • makeSceneTransitionAnimation (Activity activity, View sharedElement, String sharedElementName):可能是最常用的,场景动画,从一个场景转到另一个场景,需要共享元素sharedElement协助过度且其必须指定sharedElementName即目标Activity共享元素的transitionName
  • makeSceneTransitionAnimation (Activity activity, Pair...<View, String> sharedElements):意义与上面相同,只是支持多个共享元素

场景动画核心框架

  • Scene:用来保存场景应用中View 的属性集合
  • Transition:负责元素的过渡,你可以在不同场景根据属性值操作元素打造不同的动画
    • 普通过渡

      • Explode:从场景中心进入或移除,一种爆炸的感觉
      • Fade:最熟悉的淡入淡出
      • Slide:从场景边缘进入或移除
      • AutoTransition:默认过渡,Fade+ChangeBounds
    • 共享元素过渡
      • ChangeBounds:根据场景前后布局界限变化执行过渡动画
      • ChangeClipBounds:根据场景前后getClipBounds变化执行过渡动画
      • ChangeImageTransform:根据场景前后ImageView的矩阵变化执行过渡动画
      • ChangeScroll:根据场景前后目标滚动属性的变化执行过渡动画
      • ChangeTransform:根据场景前后视图缩放和旋转变化执行过渡动画,当然也可以根据父视图的改变
    • 场景切换调用[共享元素添加SharedElement]
      • setEnterTransition:A->B,B进入的过渡
      • setExitTransition:A->B,A退出的过渡
      • setReturnTransition:B->A,B返回的过渡
      • setReenterTransition:B->A,A重进的过渡
  • TransitionManager:把上面Scene和Transition结合起来,常见的有通过setTransition(Scene, Transition)结合

实战

前一小节貌似有点翻译的味道,但很多都是我亲自体验总结的,同时这也是必不可少的,我们要理论结合实践,再从实践中领悟真理。

场景分析

以联系人列表页为出发点,点击条目跳转到联系人详情页。期间发生了什么呢?

列表页条目的头像(其实过渡的时候已经是详情页的头像了)以贝塞尔曲线路径移动且缩放至详情页的头像;地址和名字平滑过渡到详情页;在共享元素过渡的时候,下一个场景也开始了进场动画,右下角的菜单跟小球一样弹跳下来同时背景以屏幕中心以圆形向外扩散。

代码分析

首先我们先分析一下进场动画,因为这个相对比较好理解。

public class CircularRevealTransition extends Visibility {private static final String PROPNAME_ALPHA = "crazysunj:circularReveal:alpha";private static final String PROPNAME_RADIUS = "crazysunj:circularReveal:radius";private static final String PROPNAME_TRANSLATION_Y = "crazysunj:circularReveal:translationY";@Overridepublic void captureStartValues(TransitionValues transitionValues) {super.captureStartValues(transitionValues);transitionValues.values.put(PROPNAME_ALPHA, 0.2f);final View view = transitionValues.view;transitionValues.values.put(PROPNAME_RADIUS, 0);transitionValues.values.put(PROPNAME_TRANSLATION_Y, -view.getBottom());}@Overridepublic void captureEndValues(TransitionValues transitionValues) {super.captureEndValues(transitionValues);transitionValues.values.put(PROPNAME_ALPHA, 1.0f);final View view = transitionValues.view;int radius = (int) Math.hypot(view.getWidth(), view.getHeight());transitionValues.values.put(PROPNAME_RADIUS, radius);transitionValues.values.put(PROPNAME_TRANSLATION_Y, 0);}@Overridepublic Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) {if (null == startValues || null == endValues) {return null;}final int id = view.getId();switch (id) {case R.id.satellite_menu:int startTranslationY = (int) startValues.values.get(PROPNAME_TRANSLATION_Y);float startAlpha = (float) startValues.values.get(PROPNAME_ALPHA);int endTranslationY = (int) endValues.values.get(PROPNAME_TRANSLATION_Y);float endAlpha = (float) endValues.values.get(PROPNAME_ALPHA);PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", startTranslationY, endTranslationY);PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", startAlpha, endAlpha);ObjectAnimator menuAnim = ObjectAnimator.ofPropertyValuesHolder(view, translationY, alpha);menuAnim.setInterpolator(new BounceInterpolator());menuAnim.setDuration(1000);return menuAnim;case R.id.cool_bg_view:int startRadius = (int) startValues.values.get(PROPNAME_RADIUS);int endRadius = (int) endValues.values.get(PROPNAME_RADIUS);Animator coolAnim = new NoPauseAnimator(ViewAnimationUtils.createCircularReveal(view, view.getWidth() / 2, view.getHeight() / 2, startRadius, endRadius));coolAnim.setDuration(1000);coolAnim.setInterpolator(new AccelerateDecelerateInterpolator());return coolAnim;default:return null;}}@Overridepublic Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) {...}...
}
复制代码

captureStartValues用来保存动画需要的初始值,key-value的形式,至于key可以模仿系统以两个':'分离,captureEndValues用来保存动画需要的结束值。我们这里,小球透明度从0.2开始,同时从相同x的屏幕顶部掉落,背景初始的半径为0;结束时小球透明度正常,小球回到老地方,背景扩展至满屏,这里半径取了背景宽高的平方和的二次方根,关于值大家可以自己调。

重点是继承了Visibility,像这种进场动画最好继承Visibility,因为它很好地提供了view的出现和消失方法。

我们再来看看onAppear方法,我们利用id来标识一个view,但这样就不灵活了。我们利用PropertyValuesHolder把translationY和alpha在一个view上同时执行,像这种针对同一个view且需要执行多个属性动画,就可以采用PropertyValuesHolder。背景的圆形扩散可以用ViewAnimationUtils来创建,这是Android提供的,必属精品。传入操作的view,扩散点坐标以及起始和结束半径。

onDisappear就是一个相反的过程,就不介绍了。我们再来看看共享元素动画代码:

public class BezierTransition extends Transition {...public BezierTransition() {setPathMotion(new PathMotion() {@Overridepublic Path getPath(float startX, float startY, float endX, float endY) {Path path = new Path();path.moveTo(startX, startY);float controlPointX = (startX + endX) / 4;float controlPointY = (startY + endY) * 1.0f / 2;path.quadTo(controlPointX, controlPointY, endX, endY);return path;}});}private void captureValues(TransitionValues transitionValues) {Rect rect = new Rect();transitionValues.view.getGlobalVisibleRect(rect);transitionValues.values.put(PROPNAME_SCREEN_LOCATION, rect);}@Overridepublic void captureStartValues(@NonNull TransitionValues transitionValues) {captureValues(transitionValues);}@Overridepublic void captureEndValues(@NonNull TransitionValues transitionValues) {captureValues(transitionValues);}@Overridepublic Animator createAnimator(ViewGroup sceneRoot,TransitionValues startValues, TransitionValues endValues) {if (null == startValues || null == endValues) {return null;}final int id = startValues.view.getId();if (id <= 0) {return null;}Rect startRect = (Rect) startValues.values.get(PROPNAME_SCREEN_LOCATION);Rect endRect = (Rect) endValues.values.get(PROPNAME_SCREEN_LOCATION);final View view = endValues.view;Path path = getPathMotion().getPath(startRect.centerX(), startRect.centerY(), endRect.centerX(), endRect.centerY());return ObjectAnimator.ofObject(view, new PropPosition(PointF.class, "position", new PointF(endRect.centerX(), endRect.centerY())), null, path);}...
}
复制代码

首先发现我们继承的是Transition,其次在构造函数里面我们执行了setPathMotion方法。PathMotion是Transition的一种扩展,提供以某种路径运动,有两个实现类ArcMotion和PatternPathMotion,感兴趣的可以研究下它的算法。前者是三阶贝塞尔曲线,后者是矢量曲线。

我们先来看看我们自己实现的有点low的算法,哈哈,就是一个简单的贝塞尔路径,难点可能是控制点的计算,如何让路径更优雅,这个艰巨的任务就交给你们了。

如果继承Transition,我们实现的是createAnimator,拿到我们创建的path,通过ObjectAnimator传入PropPosition实现的。

static class PropPosition extends Property<View, PointF> {PointF endPoint;PropPosition(Class<PointF> type, String name, PointF endPoint) {super(type, name);this.endPoint = endPoint;}@Overridepublic void set(View view, PointF value) {int x = Math.round(value.x);int y = Math.round(value.y);int startX = Math.round(endPoint.x);int startY = Math.round(endPoint.y);int transY = y - startY;int transX = x - startX;view.setTranslationX(transX);view.setTranslationY(transY);}@Overridepublic PointF get(View object) {return null;}
}ObjectAnimator ofObject (T target, Property<T, V> property, TypeConverter<PointF, V> converter, Path path)
复制代码

可能ObjectAnimator的这个方法我们不常用,其实也很简单,就是根据传入的path,每个进度会回调一个对象,我们这里是PointF,由于默认的就是PointF,所以我们第三个参数传null就行了。假设我们需要回调的是Point对象,那么我们要实现一个TypeConverter<PointF, Point>,很好理解,就是用来类型转换的。

PropPosition的set方法回调中会返回每个进度根据path计算出来的PointF,这样我们就可以通过结束点计算出view需要平移的距离。

我知道大家很好奇是怎么传值的,这里我单独写了一篇文章玩一玩Android属性动画源码来辅助大家理解。

回到我们的主题,既然类已经写完了,如何调用的呢?

回到我们的主题,既然类已经写完了,如何调用的呢?

//详情页
private void initTransition() {Window window = getWindow();TransitionSet set = new TransitionSet();AutoTransition autoTransition = new AutoTransition();autoTransition.excludeTarget(R.id.ic_head, true);autoTransition.addTarget(R.id.tx_name);autoTransition.addTarget(R.id.ic_location);autoTransition.addTarget(R.id.tx_location);autoTransition.setDuration(600);autoTransition.setInterpolator(new DecelerateInterpolator());set.addTransition(autoTransition);BezierTransition bezierTransition = new BezierTransition();bezierTransition.addTarget(R.id.ic_head);bezierTransition.excludeTarget(R.id.tx_name, true);bezierTransition.excludeTarget(R.id.ic_location, true);bezierTransition.excludeTarget(R.id.tx_location, true);bezierTransition.setDuration(600);bezierTransition.setInterpolator(new DecelerateInterpolator());set.addTransition(bezierTransition);CircularRevealTransition transition = new CircularRevealTransition();transition.excludeTarget(android.R.id.statusBarBackground, true);window.setEnterTransition(transition);window.setReenterTransition(transition);window.setReturnTransition(transition);window.setExitTransition(transition);window.setSharedElementEnterTransition(set);window.setSharedElementReturnTransition(set);
}
复制代码

根据前面知识普及,我相信不需要解释太多,可能需要解释的地方就是addTarget和excludeTarget。addTarget就是指定参与过渡的view,excludeTarget就是排除过渡的view。

有些小伙伴可能对那个背景很好奇,它并不是一张背景图,中间以贝塞尔曲线分割,下面是白色,上面是前一个场景高斯模糊。贝塞尔曲线虽然不是什么很新鲜的东西,但是运用广泛,比如lottie章节开发用的AE中钢笔的运用就是贝塞尔曲线。

小总结

整个过程下来,是不是发现转场动画也并不难,有些同学看到这可能已经自己写了几个过渡动画了。这我就很开心了,能帮到大家真正运用这些知识。当然了不要忘了添加windowContentTransitions属性哦,还有windowAllowEnterTransitionOverlap和windowAllowReturnTransitionOverlap来控制两个场景的动画要不要同步。NM的,咋不早说?

卫星菜单

我记得在我毕业的时候,这玩意挺火的,看起来也很牛皮,实则实现起来很简单。可以说是动画的入门,那为啥要放这里呢?我就放这里,没说我要分析啊,告诉一下大家项目有这个动画。

骚聊

又到了紧张刺激的骚聊环节。到这里,肯定有小伙伴质疑我了,你确定这是Android动画全家桶吗?我可以很负责任的告诉你,是。只不过是普通规格的全家桶。无论是动画的种类还是动画的用法肯定是不全的,但是常用的已经八九不离十,重点是给大家总结个大概,感兴趣的可以深入了解。

趁这次机会,我回答一下,很多同学问我的问题,Android到什么地步才算厉害?首先我觉得这种问题很无聊,其次我自己的水平也并不高,不知道有没有资格回答。

鄙人在这发表一得之见,很多同学可能认为懂底层源码的人才是牛皮。懂底层确实很牛皮,但不是最牛皮的,只要你有耐心去阅读分析,不断深入,我相信你也可以和大佬一样写出深刻的源码分析,可能用的时间比大佬长那么一点,那么结果就出来了,学习能力。在这技术不断迭代更新的时代,最重要的是学习能力,因为可能你今天用的技术明天就被弃用了,当然这可能夸张一点,正如今天的Android动画分享,学习能力强的人看一遍自己过一遍已经可以举一反三了,再则,源码也是人写的,也是有迭代的,那你是不是需要重新分析一遍?看源码更多的是看大牛如何写代码,然后学以致用,今天主题动画的难点不是源码也不是如何使用,而是动画的创意!

哦,对了,我答应别人每天只能吹#个牛,祝大家生活愉快,溜了,溜了。有问题可以加入Android工程师交流QQ群:752016839 主要针对Android开发人员提升自己,突破瓶颈,相信你来学习,会有提升和收获。在这个群里会有你所需要的内容,朋友们请抓紧时间加入进来吧

最后,感谢一直支持我的人!

来一份Android动画全家桶(下篇)相关推荐

  1. 来一份Android动画全家桶

    前言 自上次<MTRVA2.0来啦>发布后,马上就有小伙伴问我有哪些Android动画,过了一段时间又有小伙伴问我啥时候发布Android动画.其实,在写<MTRVA2.0来啦> ...

  2. 谷歌技术团队出品,Android Flutter全家桶学习资料【全新版】

    Flutter 是谷歌的移动端 UI 框架,可在极短的时间内构建 Android 和 iOS 上高质量的原生级应用. Flutter 可与现有代码一起工作, 它被世界各地的开发者和组织使用, 并且 F ...

  3. Google Android GMS全家桶

    Google Android设备Gms全家桶app和包名对应关系统计,供查询. Apk名称 包名 AppName 中文名称 说明 AndroidAutoStub com.google.android. ...

  4. react安装_超全面详细一条龙教程!从零搭建React项目全家桶(上篇)

    React是近几年来前端项目开发非常火的一个框架,其背景是Facebook团队的技术支持,市场占有率也很高.很多初学者纠结一开始是学react还是vue.个人觉得,有时间的话,最好两个都掌握一下.从学 ...

  5. android9.0 谷歌全家桶,Motorola P30 NOTE/Play刷国际版9.0(Android One)教程

    摩托罗拉和火腿肠真的是有异曲同工之处,他们都是因为"傲慢"把自己玩死的!想当年moto x可谓是红极一时啊,那个手感个人感觉比iPhone 3gs还舒服!再刷个CM真的是美滋滋地用 ...

  6. 使用react全家桶制作博客后台管理系统

    前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基于react全家桶(React.React-r ...

  7. [转]vue全面介绍--全家桶、项目实例

    慢慢了解vue及其全家桶的过程 原文http://blog.csdn.net/zhenghao35791/article/details/67639415 简介 "简单却不失优雅,小巧而不乏 ...

  8. React全家桶写一个CNode社区,奉上心得与源码

    webpack2+react+react-router+react-redux+ES6+antd-mobile版本的Cnode 苦于我就职的公司的技术栈还是ES5+jQ+handelbars, 而我一 ...

  9. 2021年最新UI/UE设计软件全家桶

    2021年最新最全的UI/UE设计软件全家桶新鲜出炉了.知识体系完备,从小白到大神各阶段读者均能学有所获.生动形象,化繁为简,讲解通俗易懂.结合工作实践及分析应用,培养解决实际问题的能力.学习资源充足 ...

最新文章

  1. 青少年编程竞赛交流群第050次活动录播
  2. python 比较文件夹或列表异同
  3. 能源项目xml文件 -- springMVC-servlet.xml
  4. 计算机硬盘按不同接口,硬盘接口不同 速度差别竟然这么大
  5. 关于MapReduce单词统计的例子:
  6. python后台架构Django开发全解
  7. c语言指针详解pdf下载,C语言指针详解.pdf
  8. 小鸡啄米之React事件处理
  9. 《程序员修炼之道-从小工到专家》读后感
  10. Spark Streaming背压机制
  11. contiki学习笔记(四)、contiki系统UDP通信原理(单播、多播、RPL介绍)
  12. lzx和网页之间脚本交互调试方式
  13. 数据还原(recover)
  14. 零基础想要快速的学好3D游戏建模,兼职接单私活,来看业内人士的分析
  15. 微分方程解析解+数值解
  16. 支付宝推出AR实景红包,开启红包新玩法!
  17. 三菱M80操作介绍_CIMT2019 展品预览:三菱电机以“智能制造 价值创造”为主题参展...
  18. 如何在Server 2003查看 是 32位还是64位
  19. 双目是个词吗_描写眼睛的词语3个字
  20. Opengl ES之PBO

热门文章

  1. 【狂神说Java】---JavaWeb
  2. 组件Element的入门学习
  3. MySQL学习笔记——基础语句
  4. 风电场气象服务器是什么系统,风电场气象参数采集与管理系统
  5. Linux命令001:find、xargs、grep查找内容
  6. x509数字证书详解
  7. 市场调研-环保型烟花市场现状及未来发展趋势
  8. 【1999年分区联赛提高组之一】【图论】【最小点覆盖】【匈牙利】拦截导弹
  9. SpringBoot 项目上传文件异常【java.io.IOException: Stream closed】
  10. HC32L136国产超低功耗华大MCU芯片介绍