HollyTransition

零、导读

深入解读Android过渡动画Transition:

页面切换动画(过场动画)

共享元素动画

延时动画

场景动画

一、Transition前世今生

为了支持各种交互视觉设计的不断更新,Android对于开发者提供了越来越多的动画API支持。从API 1就存在的Drawable Animation和View Animation,以及API 11(Android 3.0)以后加入的Property Animation。而过渡动画Transition是在API 19(Android 4.4.2)中加入的。说道炫酷的动画,很多人首先想到的是Android5.0开发流行起来的共享元素动画。然而共享元素动画只是Transition的冰山一角。今天就让我们来一一揭开。

二、为什么要引入Transition

那为什么要引入Transition动画呢?由于在Android引入了Metrial Desigon之后,动画的场面越来越大,比如以前我们制作一个动画可能涉及到的View就一个,或者就那么几个,如果我们一个动画中涉及到了当前Activity视图树中的各个View,那么情况就复杂了。比如我们要一次针对视图树中的10个View进行动画,这些View的效果都不同,可能有的是平移,有的是旋转,有的是淡入淡出,那么不管是使用之前哪种方式的动画,我们都需要为每个View定义一个开始状态和结束状态【关键帧,比如放缩,我们得设置fromXScale和toXScale 】,随着View个数的增加,这个情况会越来越复杂。这个时候如果使用一堆Animator去实现这一连串动画,代码将会又臭又长,Transition的出现大大减轻了开发的工作。

三、页面切换动画(过场动画)

以前使用过场动画时,实现共享元素,主要是使用Activity,但是有遇到很多坑:比如最严重的一个是在某些机型上出现做返场动画时,第二个页面的画面还残留在页面上;第二个问题是,在存在虚拟导航栏的手机上,由于不同Activity是在不同的Window上绘制的,在Activity切换时界面存在闪烁的情况。而Fragment的切换则不存在这样的问题。综合考虑,决定使用Fragment。

示例图1:入场动画

示例图2:返场动画

三(1)、共享元素动画

如上图中 画廊按钮、拍摄按钮、设置按钮 ;页面切换时,对共享元素动画来讲,重要的函数有以下几个。共享元素执行的动画主要是针对将要进入的页面的。一个是入场动画 EnterTransition,另一个是返场 ReturnTransition。在做共享元素动画时,需要在前后两个页面对共享元素设定TransitionName,且对应的两个View,TransitionName需要相同。

//在第二个页面(即要进入的那个页面)设置共享元素TransitionName ViewCompat可兼容5.0以下版本,不报错

ViewCompat.setTransitionName(mFakeLeft, SHARE_NAME_LEFT);

ViewCompat.setTransitionName(mPhotoBtn, SHARE_NAME_MIDDLE);

ViewCompat.setTransitionName(mFakeRight, SHARE_NAME_RIGHT);

//设定入场动画和返场动画

fragment.setSharedElementEnterTransition(new ChangeBounds());

fragment.setSharedElementReturnTransition(new ChangeBounds());

//添加共享元素

FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();

fragmentTransaction.replace(R.id.app_ctrl_view, fragment);

fragmentTransaction.addToBackStack(null);

if (shareViews != null && shareViews.length > 0) {

for (Pair shareView : shareViews) {

fragmentTransaction.addSharedElement(shareView.first, shareView.second);

}

}

fragmentTransaction.commit();

疑问:上图的共享元素是如何实现形变的过程中发生alpha变化的?

刚开始,准备直接使用 xml 定制动画,一个changeBounds(左中右三个按钮同时形变) + 一个 fade (左右按钮在形变的同时 fadeout),但是发现只有形变生效。于是尝试使用自定义ChangeBounds来改造共享元素动画,在createAnimator中拿到Animator,从而可以拿到Animator变化的过程 ValueAnimator.AnimatorUpdateListener,这样就可以实现形变的同时,发生alpha的变化。 示例代码

// 改造需要了解Transition的三大核心方法如下。

@Override

public void captureStartValues(TransitionValues transitionValues) {

super.captureStartValues(transitionValues);

Log.d(TAG, "captureStartValues: transitionValues=" + transitionValues);

}

@Override

public void captureEndValues(TransitionValues transitionValues) {

super.captureEndValues(transitionValues);

Log.d(TAG, "captureEndValues: transitionValues=" + transitionValues);

}

@Override

public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {

return super.createAnimator(sceneRoot, startValues, endValues);

}

三(2)、内容动画

如上面图片中,设置页面的背景渐变、动起来 SlideUp、功能说明 SlideUp;拍摄页面的进度条封面 Slide Left&Right。这里所说的内容动画,指的是Fragment布局中除共享元素外的View执行的动画,主要是为了区分共享元素动画。对内容过场动画比较重要的几个函数,下面这种图比较形象地描述这个关系。Activity和Fragment的意思是一样的。这里以Activity的示意图来说明。

示例图3:入场动画对应关系

示例图4:返场动画对应关系

TransitionSet transitionSet=new TransitionSet();

//从 A Fragment 进入 B Fragment,A会执行 ExitTransition,B 会执行EnterTransition

fragment.setEnterTransition(transitionSet);

fragment.setExitTransition(transitionSet);

//按返回键时,B Fragment 会pop出栈,执行ReturnTransition,此时 A Fragment 重新回到栈顶,执行ReEnterTransition.

fragment.setReenterTransition(transitionSet);

fragment.setReturnTransition(transitionSet);

四、延时动画(TransitionManager.beginDelayedTransition)

四(1)、进度条平滑过渡

iOS进度条有个方法:- (void)setProgress:(float)progress animated:(BOOL)animated; 如果animated=true 那么,设置进度时,会从当前进度平滑过渡到目标进度,大大提升了用户体验。Android进度条并不具备这种功能,只能设定一个progress,控件就僵硬地跳到对应的位置。但是借助Transition,可实现这个功能。 示例代码

示例图5:实现进度条平滑过渡

四(2)、放大缩小效果

示例图6:实现同一ViewGroup中两个View之间协调放大与缩小效果

四(3)、Slide与Fade组合动画

示例图7:从上滑入从下滑出并且渐入渐出

五、路径动画

路径动画在日常APP中使用的场景很多,比如京东、天猫 添加商品至购物车的动画。实现起来非常简单。直接使用Transition.setPathMotion(new ArcMotion());然后结合延时动画TransitionManager.beginDelayedTransition()即可。

示例图片8:普通路径动画

示例图片9:路径动画在共享元素中的运用

六、源码解析

入场动画时,是直接拿着 进入页面的View进行动画的,返场动画时,不直接拿着View进行动画,而是在Overlap上,创建与Targets对应的ImageView,然后截取Targets的画面,显示在ImageView上,返场动画主要是在Overlap上进行的。

public Animator onDisappear(ViewGroup sceneRoot,

TransitionValues startValues, int startVisibility,

TransitionValues endValues, int endVisibility) {

if ((mMode & MODE_OUT) != MODE_OUT) {

return null;

}

View startView = (startValues != null) ? startValues.view : null;

View endView = (endValues != null) ? endValues.view : null;

/**

* 说明几点:

* #1 onDisappear方法位于android.transition.Visibility类中

* #2 在返场动画时,场景中每个被添加进Transition的Target都会执行该方法

* #3 这里定义了一个overlayView,先标记下,后面会用得到

*/

View overlayView = null;

View viewToKeep = null;

if (endView == null || endView.getParent() == null) {

if (endView != null) {

// endView was removed from its parent - add it to the overlay

overlayView = endView;

} else if (startView != null) {

// endView does not exist. Use startView only under certain

// conditions, because placing a view in an overlay necessitates

// it being removed from its current parent

if (startView.getParent() == null) {

// no parent - safe to use

overlayView = startView;

} else if (startView.getParent() instanceof View) {

/**

* 如果StartView存在父布局

*/

View startParent = (View) startView.getParent();

TransitionValues startParentValues = getTransitionValues(startParent, true);

TransitionValues endParentValues = getMatchedTransitionValues(startParent,

true);

VisibilityInfo parentVisibilityInfo =

getVisibilityChangeInfo(startParentValues, endParentValues);

if (!parentVisibilityInfo.visibilityChange) {

/**

* 如果StartView父布局在动画过程中未参与Visibility变化的话,那么就会

* 创建一个ImageView,并将StartView 画布Canvas上的内容转换为Bitmap设置到新创建的ImageView上

*/

overlayView = TransitionUtils.copyViewImage(sceneRoot, startView,

startParent);

} else if (startParent.getParent() == null) {

int id = startParent.getId();

if (id != View.NO_ID && sceneRoot.findViewById(id) != null

&& mCanRemoveViews) {

// no parent, but its parent is unparented but the parent

// hierarchy has been replaced by a new hierarchy with the same id

// and it is safe to un-parent startView

overlayView = startView;

}

}

}

}

} else {

// visibility change

if (endVisibility == View.INVISIBLE) {

viewToKeep = endView;

} else {

// Becoming GONE

if (startView == endView) {

viewToKeep = endView;

} else {

overlayView = startView;

}

}

}

final int finalVisibility = endVisibility;

final ViewGroup finalSceneRoot = sceneRoot;

if (overlayView != null) {

// TODO: Need to do this for general case of adding to overlay

int[] screenLoc = (int[]) startValues.values.get(PROPNAME_SCREEN_LOCATION);

int screenX = screenLoc[0];

int screenY = screenLoc[1];

int[] loc = new int[2];

sceneRoot.getLocationOnScreen(loc);

overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());

overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());

/**

* 通过上面一系列的判断,最终会将得到的overlayView添加到 场景一(及A页面)根部局的Overlap上

* 也就是说,其实在做返场动画时,所有的动画都是在 A页面的Overlap上进行的。做完动画再将其从Overlap上移除。

* 这里的overlayView可能是 B页面的控件,也有可能是B页面控件的画面(new了一个ImageView的形式展示)

* 什么是Overlap,可参考文章:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0130/2384.html

*/

sceneRoot.getOverlay().add(overlayView);

Animator animator = onDisappear(sceneRoot, overlayView, startValues, endValues);

if (animator == null) {

sceneRoot.getOverlay().remove(overlayView);

} else {

final View finalOverlayView = overlayView;

addListener(new TransitionListenerAdapter() {

@Override

public void onTransitionEnd(Transition transition) {

finalSceneRoot.getOverlay().remove(finalOverlayView);

}

});

}

return animator;

}

if (viewToKeep != null) {

int originalVisibility = viewToKeep.getVisibility();

viewToKeep.setTransitionVisibility(View.VISIBLE);

Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues);

if (animator != null) {

DisappearListener disappearListener = new DisappearListener(viewToKeep,

finalVisibility, mSuppressLayout);

animator.addListener(disappearListener);

animator.addPauseListener(disappearListener);

addListener(disappearListener);

} else {

viewToKeep.setTransitionVisibility(originalVisibility);

}

return animator;

}

return null;

}

七、遇到的问题

Scene切换时会RemoveAllView 导致之前设置点击事件消失

退场动画时,会截取View的canvas,并在A页面rootview的overlap上添加ImageView,如果自定义控件有影响canvas的绘制过程,则添加到ImageView上的Bitmap可能不正确

共享元素必须是RootView的直接子View

做页面间的过渡动画时,两个页面尽量简单,比如加载网络图片时,可以先使用前一个页面的图片做动画,等动画做完后再去加载图片。不可以一边儿做动画,一边儿加载图片,会造成动画很闪烁。

Fragment设置TransitionOverlap无效,还需要研究下。

fragment.setAllowEnterTransitionOverlap(false);

fragment.setAllowReturnTransitionOverlap(false);

在Fragment中使用 共享元素动画 时,需要两个Fragment基于同一个layout_id,然后通过replace的形式打开。

要使用Transition版本必须API>=19(Android4.4),有些功能 如 Slide设定Edge、路径动画ArcMotion 更是需要 API>=21(Android 5.0)

八、TODO

图片切换平滑过渡效果

颜色变化平滑过渡

文字变化平滑过渡

深入研究Transition-EveryWhere,存在类型转化的BUG

九、参考项目

Transitions-Everywhere 可支持Transiton动画到 Android 4.O ,并且兼容 Android 2.2 +(无动画但保证运行).

详细解读Fragment与Fragment Activity与Activity Activity与Fragment之间的切换动画

早在Android 4.4,Transition 就已经引入,但在5.0才得以真正的实现。而究竟Transition是用来干嘛的呢?这个Demo可以带你熟悉基本的操作。特点是比较熟练的使用xml来定义动画,动画衔接做得比较好。

主要特点是介绍ViewPager中实现的一些Transition动画

ViewOverlay与animation介绍。Transition在ReturnTransition动画时,会在第一个页面rootView的overlap上添加ImageView来执行动画。这篇文章详细介绍了Android中ViewOverlay的用处和原理。

Transition DEMO

十、结尾

本文示例代码: git@github.com:Pluckypan/HollyTransition.git 预览 欢迎Star、Fork

android 延时播放动画,HollyTransition: 让APP丝滑般流畅:深入解读Android过渡动画Transition. 共享元素动画、场景动画、过场动画、延时动画...相关推荐

  1. 用树莓派官方摄像头做丝滑般流畅的监控!

    1.安装辅助工具 在树莓派上执行: sudo apt-get install libjpeg8-dev sudo apt-get install cmake 2.解压master,zip 在树莓派上执 ...

  2. 【使用篇】WebView 实现嵌套滑动,丝滑般实现吸顶效果,完美兼容 X5 webview

    本文首发我的公众号徐公,收录于 Github·AndroidGuide,这里有 Android 进阶成长知识体系, 希望我们能够一起学习进步,关注公众号徐公,5 年中大厂程序员,一起建立核心竞争力 背 ...

  3. 揭秘双11丝滑般剁手之路背后的网络监控技术

    简介:本篇将重点介绍Hologres在阿里巴巴网络监控部门成功替换Druid的最佳实践,并助力双11实时网络监控大盘毫秒级响应. 概要:刚刚结束的2020天猫双11中,MaxCompute交互式分析( ...

  4. vue可视化拖拽组件模板_基于 Vue 丝滑般拖拽排序组件VueSlicksort

    今天给大家分享一个功能超强的自由拖拽排序组件VueSlicksort. vue-slicksort 一款功能强大的可拖拽的vue.js组件.拥有丝滑般拖拽动画效果,支持水平/垂直/网格拖拽排序.还可以 ...

  5. xp精简工具_Windows10你也可以精简优化,丝滑般极爽轻松做到,再也不卡了

    Windows10大家也比较熟悉了,都在使用,有的喜欢有的厌恶,喜欢的肯定是非常熟练操作Windows10了,厌恶呢一般是不了解Windows10操作,不想去接触新生事物,Windows7都已经够用了 ...

  6. ant vue 树形菜单横向显示_丝滑般 Vue 拖拽排序树形表格组件Vue-DragTreeTable

    今天给小伙伴们分享一款纵享丝滑般体验的Vue拖拽树形表格DragTreeTable. vue-drag-tree-table 基于vue.js实现可拖拽排序的树形表格组件.支持拖拽排序.固定表头.拖拽 ...

  7. 让代码丝滑般跳转,rust-analyzer,你值得拥有

    1 RLS触怒了我 我是一个专一的人,从学习Rust起就在vscode中使用rls作为跳转插件(主要原因其实是懒),如果不是今天它彻底触怒了我,恐怕我还会对它继续钟情下去. 事情的原委是这样的,今天下 ...

  8. 深入浅出讲解丝滑般动画特效实现原理

    作者丨哈哈将 个推安卓高级开发工程师 来源丨个推技术学院 (getuitech) 前言 APP开发市场已经告别"野蛮生长"时代,人们不再满足于APP外形创新,而将目光转向全方面的用 ...

  9. android 活动切换动画,android – 在使用ChangeImageTransform共享元素转换的两个活动之间动画化ImageView...

    要在具有共享元素的两个活动之间进行屏幕转换动画, 您可以阅读 this article并按照上述步骤: Enable window content transitions in your theme. ...

最新文章

  1. java中的.运算符_java中的各种运算符
  2. android获取系统当前年月日时分秒的时间
  3. embed 标签怎么嵌入pdf_联合Aspect-Sentiment主题嵌入的弱监督的情感分析(2020年10)
  4. 20162303 队列加分项-杨辉三角
  5. glyphicons-halflings-regular.woff2 文件 404
  6. 二阶等差数列的性质及应用
  7. STM32使用MCUISP下载程序教程
  8. 使用phpStudy搭建74cms(详)
  9. python 检查域名是否可以访问_利用Python实现DGA域名检测
  10. 订单生成列表html,订单列表.html
  11. golang做php的中间件,Golang 之 中间件
  12. 记录下如何用vue实现PC端网易云轮播图效果
  13. Gnuplot 常用命令
  14. 使用mac制作linux启动盘与恢复U盘(dd命令制作U盘启动盘后怎么恢复U盘)
  15. java基础之this关键字_繁星漫天_新浪博客
  16. 二、第十五届全国大学生智能汽车竞赛AI电磁——硬件设计篇
  17. 天津二级计算机考试地点,2016年9月天津计算机一级二级三级四级考点地址电话...
  18. 2022.10.4 英语背诵
  19. sd firmware
  20. 高安全等级网络安全防护体系研究与设计

热门文章

  1. Java基础-02(基础语法)
  2. DNSPod十问周康:如何成为办公硬件领域的乔布斯?
  3. 给女友写个代码c语言,写给女友的一段c编程!不求技术,只求一点小惊喜!
  4. ios 协议中添加属性---分解ZFPlayer
  5. Error resolving template XXX, template might not exist or might not be accessible by any of the.....
  6. 【Python】弹球小游戏
  7. Spring Security 官网文档学习
  8. 图解 赫夫曼编码?(赫夫曼大叔开讲啦!!!)
  9. periphery SPM ( 做本地, local )
  10. [附源码]SSM计算机毕业设计校园闲置物品租赁系统JAVA