最近用比较快的速度(其实还可以更快)在 Android 上实现了个小工具。这个工具小得简直不值一提,但是很有意思的是,中间出了几个微妙的状况。
第一个是跟系统申请到的辅助功能经常掉。不管是华为还是三星,没多久就会把服务停掉,导致执行预期操作时频繁需要让用户再手动开启系统的开关。在这个问题上三星尤其不留情面,几乎一到晚上,必杀。第二天早上起来就会发现辛辛苦苦全白干、一夜退回解放前。后来调整了系统优化策略,发现有了非常好的回馈,三星甚至比华为上的效果还要好。
第二个就更郁闷,比第一个还吊诡。系统的开关状态正常,但是就是执行预期操作时得不到预期结果。在网上到处找信息,后来发现了一个疑似相关的帖子。连忙赶出来个方案,写代码打包测试,目前为止似乎是走对路子了,但仍然需要持续观察。这个问题在华为手机上表现突出,中间还在 logcat 的日志流中观察到另一个错误,一度以为是相关的甚至那就是根源,后来才发现大概率是风牛马不相及的。那些错误的信息通常如下:
channel ‘d30197b com.hello.world/com.hello.world.MainActivity (client)’ ~ receive message failed, errno=11, seq = 2357504
channel ‘d30197b com.hello.world/com.hello.world.MainActivity (server)’ ~ receive message failed, errno=11, seq = 0
最后一个是闪屏。这个还没找到原因,只是有些眉目。在找资料的过程中,发现有位网友对 overridePendingTransition
的审视和了解相对完整,除记录下链接之外,为了防止遗失,全文摘录如下。
Android Activity 跳转动画 – overridePendingTransition 用法及原理分析
ccpat 于 2018-12-09 21:12:52 发布
overridePendingTransition()
是在 Activity
类中实现的一个用来实现跳转动画的方式,也是最常使用的方法。
overridePendingTransition()
先看这个名字。这个方法名很长,由三个单词组成,override 是重写、覆盖的意思,pending 表示即将到来的,transtition 表示过渡、转换,也就是过渡动画的意思。三个单词连起来的意思就是覆盖即将到来的跳转动画,也就是可以通过这个方法添加的跳转动画会覆盖掉即将到来的跳转动画效果。即将到来的这个词很有意思,它表示即使不使用 overridePendingTransition()
,也会存在 Activity 切换动画,这个切换动画可能来自于其他方式添加的跳转动画,也可以来自于系统自带的默认动画。事实上,即使不通过本文介绍的任何方法添加 Activity 跳转动画,Activity 也会有默认的跳转动画,这个默认的跳转动画定义在 Android 主题中。在 android:Theme
主题中可以看到这样一句样式定义,正是这句为 App 中所有 Activity 添加了一个默认的切换动画。
1 |
<item name="windowAnimationStyle">@style/Animation.Activity</item> |
不过需要注意的是,@style/Animation.Activity
中定义的动画样式在不同的系统版本上会有所不同,此外部分 Theme 自定义了新的 windowAnimationStyle
,所以默认的 Activity 动画并非一定是 @style/Animation.Activity
中定义的样子。
overridePendingTransition() 使用方法
Activity 的切换动画从业务层面上来说可以分为两种,一种是 Activity 启动时的动画,一种是从 Activity 返回时的动画,它们都可以通过 overridePendingTransition()
来设置,要设置启动时的动画需要在执行 startActivity()
或 startActivityForResult()
之后调用 overridePendingTransition()
,要设置返回时的动画需要在 finish()
之后调用 overridePendingTransition()
。启动动画和返回动画是相互独立的,设置启动动画不会对返回动画产生影响,如果只在 startActivity()
或 startActivityForResult()
之后调用了 overridePendingTransition()
,没有在 finish()
的时候调用,则 Activity 返回的时候仍然是默认的动画效果,也可以在 finish()
的时候使用和启动时不同的动画效果。
由于 startActivity()
和 startActivityForResult()
本质上是一样的,为了表述方便,后文不再提到 startActivityForResult()
,所有使用 startActivity()
的地方都可以用 startActivityForResult()
。
overridePendingTransition()
只能作用在通过 startActivity()
和 finish()
方式启动和结束 Activity 的场景下,其他情况下的 Activity 创建和退出是不会有任何效果的。例如通过 recreate()
重建 Activity,在其后执行 overridePendingTransition()
是没有动画效果的。
overridePendingTransition() 的调用时机
注释和官方文档中关于此方法调用时机都写到“Call immediately after one of the flavors of startActivity(Intent) or finish to specify an explicit transition animation to perform next.”,也就是说此方法需要在 startActivity()
或者 finish()
方法之后立即调用。因此标准的写法应当是这样的。
1 2 3 4 5 6 7 8 9 10 11 |
Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent); overridePendingTransition(R.anim.anim_down_in, R.anim.anim_down_out); mSomeButton.setOnClickListener(new View.OnClickListener() { @override public void onClick(View v) { finish(); overridePendingTransition(R.anim.anim_down_in, R.anim.anim_down_out); } }); |
对一个 Android Activity 来说,除了可以调用 finish()
来结束之外,它还可以被 Android 的返回键所结束。在按下返回键后,会执行到 onBackPressed()
方法,在 onBackPressed()
方法中会自动调用 finish()
方法结束 Activity。所以在添加 Activity 返回动画时,除了要在所有调用 finish()
的地方调用 overridePendingTransition()
之外,还要重写 onBackPressed()
方法,在执行 super.onBackPressed()
之后调用 overridePendingTransition()
。由于 onBackPressed()
总是在最后一步执行 finish()
,所以如下代码和执行完 finish()
后调用 overridePendingTransition()
是等同的。
1 2 3 4 5 |
@override public void onBackPressed() { super.onBackPressed(); overridePendingTransition(R.anim.anim_down_in, R.anim.anim_down_out); } |
immediately 的含义通常被理解为调用 startActivity()
或 finish()
之后的下一行代码就必须调用 overridePendingTransition()
才能让动画效果生效。但事实上 overridePendingTransition()
可以推迟到 startActivity()
或 finish()
之后的某个地方执行。
对启动 Activity 来说,overridePendingTransition()
通常可以放到被启动 Activity 的 onCreate()
中来执行,很多文章都说 overridePendingTransition()
只能在调用 super.onCreate()
之前执行,这大部分情况是正确的,但并不是说 overridePendingTransition()
放到 super.onCreate()
之后就一定没有效果,事实上甚至可以把 overridePendingTransition()
放到 onResume()
来执行,这在很多时候是可以有效果的。
对结束 Activity 来说,finish()
之后的 overridePendingTransition()
执行时机要严格很多,首先并不能将 overridePendingTransition()
推迟到 onDestroy()
中执行,也不能在 finish()
和 overridePendingTransition()
之间执行一些耗时的代码,但增加一些简单的处理逻辑通常是不会有问题的。
为了避免在不同设备上的差异,减少意外的发生,应当按照官方要求在 startActivity()
或 finish()
方法之后立即调用 overridePendingTransition()
,对启动 Activity 来说,最迟应当放到被启动的 Activity 的 onCreate()
方法中执行于 super.onCreate()
之前,对结束 Activity 来说,只能在 finish()
之后和 super.onBackPessed()
之后执行。
如果在 Activity 还未显示完成就调用 startActivity()
启动另一个 Activity,这时在 startActivity()
之后调用 overridePendingTransition()
来设置启动动画是不会有效果的,例如在 onCreate()
中调用 startActivity()
和 overridePendingTransition()
就不会有动画效果,这时连默认的动画效果都不会有,被启动的 Activity 会直接显示出来。
overridePendingTransition() 方法参数解析
overridePendingTransition()
方法的定义如下。
1 |
public void overridePendingTransition(int enterAnim, int exitAnim) |
它包含两个参数,分别为 enterAnim
和 exitAnim
,它们都是定义在 anim
文件夹中的某个动画资源的 id。overridePendingTransition()
方法只有一个定义,没有其他重载的方法,也就是说 overridePendingTransition()
设置的动画效果只能通过 anim 资源的方式来定义,无法通过代码创建。
enterAnim
用来设置即将进入的 Activity 的动画效果,而 exitAnim
用来设置即将退出的 Activity 的动画效果。当从 A 启动 B 时,A 是退出的 Activity,B 是进入的 Activity,当从 B 返回 A 时则正好相反,A 是进入的 Activity,B 是退出的 Activity。也就是说,从 A 启动 B 时,B 的动画效果是 enterAnim
,A 的动画效果是 exitAnim
,从 B 返回 A 时,B 的动画效果是 exitAnim
,A 的动画效果是 enterAnim
。
在 Android 系统自带了两组动画效果,分别为 fade_in/fade_out
和 slide_in_left/slide_out_right
。可以通过如下代码来使用。
1 2 3 |
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); overridePendingTransition(android.R.anim.slide_in_left, android.R.anim.slide_out_right); |
设置有如下场景,有三个 Activity,分别为 A、B 和 C,A 启动 B,B 启动了 C,但 B 启动 C 之后立刻调用了 finish()
结束了自己,代码如下。这里 overridePendingTransition()
既在 startActivity()
之后执行,又在 finsh()
之后执行,这时动画效果究竟会如何展现呢?
1 2 3 4 |
Intent intent = new Intent(B.this, C.class); startActivity(intent); finish(); overridePendingTransition(R.anim.anim_down_in, R.anim.anim_down_out); |
回答这个问题关键还在于理解 overridePendingTransition()
本身和 enterAnim
和 exitAnim
这两个参数的意义。 在前面我们有提到,Activity 的切换动画分为两种,一种是 Activity 启动时的动画,一种是从 Activity 返回时的动画,但对 overridePendingTransition()
来说,它其实并不区分这两种动画类型,无论在 startActivity()
之后还是在 finish()
之后执行,它的结果都是一致的,overridePendingTransition()
只是为即将到来的 Activity 切换添加动画效果,也就是为即将进入的 Activity 执行 enterAnim 动画,为即将退出的 Activity 执行 enterAnim 动画。所以只需要知道上述场景下哪个是即将进入的 Activity,哪个是即将退出的 Activity 即可。显然在上述场景下,C 是即将进入的 Activity,B 是即将退出的 Activity,所以动画效果就是 C 执行 enterAnim,B 执行 exitAnim。
上述 enterAnim
和 exitAnim
的值都允许为 0
,如果 enterAnim
值为 0
,表示没有 enter 动画效果,如果 exitAnim
为 0
表示没有 exit 动画效果。如果两者都为 0
,则既没有 enter 动画效果,也没有 exit 动画效果,也就是不会有切换动画。但如果两个参数只有一个为 0
,则实际显示情况会比较复杂。
这里仍以 A 和 B 两个 Activity 为例,首先看 A 启动 B 的时候,如果执行 overridePendingTransition(0, R.anim.anim_down_out);
,也就是 enterAnim
为 0
,它表示没有 enter 动画效果,这时 B 会直接显示出来,而 exitAnim
不为 0
,也就是说 A 会有一个 down_out
的动画效果,然而 B 的界面是覆盖在 A 之上的,那么显然即使 A 执行了 down_out
动画,也是无法看到这个效果的。所以这时从视觉上看是不会有任何动画效果的。我们知道 Activity 所依附的 Window 是存在背景的,正是这个背景使得我们无法透过当前 Activity 看到下层的 Activity,那么如果将 B 设置为一个透明背景的 Activity,是否意味着就可以看到 A 执行 down_out
的动画效果呢?实践结果告诉我们,如果 B 的背景是透明的,我们能够透过 B 看到 A,但在 B 直接显示出来之后,A 压根就没动,根本看不到任何动画效果。
再来看 overridePendingTransition(R.anim.anim_down_in, 0);
,由于 exitAnim
为 0
,所以 A 是没有动画效果的,而 B 会有一个 down_in
的动画效果,这时在视觉上看到的会是 B 从底部升起,但在 B 逐渐升起的过程中,上面尚未被覆盖的部分并不能看到 A 的内容,而是一大块的黑色。这时如果 B 的背景是透明的,则会发现在 B 升起的过程中没有了黑色的部分,而是能够看到 A 的内容了。
再来看 Activity 返回的时候,如果执行 overridePendingTransition(0, R.anim.anim_down_out);
,这时没有 enterAnim
效果,只有 exitAnim
,而 B 是那个退出的 Activity,所以 B 会有一个动画效果逐渐往下退出,而在 B 退出的过程中我们也是看不到 A 的,能看到的只是上方的一大块黑色。这时如果 B 是透明的,则能看到 A 就一直显示在 B 的下方,一动不动。如果执行 overridePendingTransition(R.anim.anim_down_in, 0);
,我们会看到 B 直接不可见,A 从下方升起,A 升起过程中上面同样是黑色的背景。奇怪的是,如果这时将 B 背景设置为透明,并非黑色的背景被取代,而是整个动画过程不可见。
上述过程总结如下。
1 2 3 4 5 6 7 |
| 启动 Activity | 返回 Activity ---------------+-----------------------------------------+----------------------------------------- enterAnim 为 0 | B 背景不透明:没有动画效果 | B 背景不透明:B 有动画效果,A 不可见 | B 背景透明:没有动画效果 | B 背景透明:B 有动画效果,A 可见,A 无动画效果 ---------------+-----------------------------------------+----------------------------------------- exitAnim 为 0 | B 背景不透明:B 有动画效果,A 不可见 | B 背景不透明:A 有动画效果,B 不可见 | B 背景透明:B 有动画效果,A 可见,A 无动画效果 | B 背景透明:没有动画效果 |
在之前关于 overridePendingTransition()
动画的介绍中,都认为 overridePendingTransition()
动画是以 Activity 作为对象的,进入的 Activity 执行 enterAnim
,退出的 Activity 执行 exitAnim
,但如果是这样,上述现象有几个地方会无法解释。
- 在启动 Activity,
enterAnim
为0
,B 背景透明时,为何看不到 A 执行exitAnim
- 在返回 Activity,
extiAnim
为0
,B 背景透明时,为何看不到 A 执行enterAnim
为了理解上述现象,只能从源码去看 overridePendingTransition()
究竟是如何实现 Activity 切换动画的。跟踪了一下,无奈其实现实在是过于复杂,网上资料也几乎没有,所以这里只好对其原理做个推测了。
overridePendingTransition()
并没有对 Activity 实现动画,所有的动画都是在 View 层面执行的。 overridePendingTransition()
执行的动画始终都在 B 中,无论是从 A 启动 B,还是从 B 返回 A,A 始终是不变的。当从 A 启动 B 时,A 会将自身显示做一个快照,将这个快照对象传递给 B。从 B 返回 A 时,所谓 A 的进入动画,仍然是使用之前 A 传过来的这个快照,并非使用 A 实时的显示。 在 B 中,A 的退出和进入动画都是通过背景实现的,它会将 A 传过来的快照和黑色的背景拼接在一起,按照设定的动画效果来显示,所以如果将 B 的背景设置为透明,则整个背景变得不可见,所以不会有任何 A 的动画效果。这就解释了上述问题,当 B 背景透明时,是不会看到 A 执行 extiAnim
和 enterAnim
的。 在 B 中,B 的进入和退出动画实际上是对 BcontentView
的动画效果。