8、View动画之Activity转场动画:overridePendingTransition、ActivityOptionsCompat、共享元素转场

说实话,Activity之间的跳转动画,是很多Android开发者容易忽略的细节。大家往往把精力放在界面内容上,觉得转场嘛,能跳过去就行了。但我要说,一个流畅、优雅的转场动画,真的能让App的质感提升一个档次。

我记得刚入行那会儿,带我的老大哥跟我说过一句话:「用户第一眼看到的东西,决定了ta对App的印象。」而Activity转场,恰恰就是那个「第一眼」。今天我们就来聊聊View时代的三种转场方式,它们虽然老了,但思想至今仍在Compose中延续。

核心要点: Activity转场动画的本质,是在两个Activity的启动和结束过程中,对它们的窗口进行动画操作。说白了,就是让界面切换不再生硬。

8.1 overridePendingTransition:最朴素的转场方式

这是Android 2.0就引入的老方法。你调用startActivity()finish()之后,紧跟着调用它就行。它接受两个参数:进入动画和退出动画。

// 启动新Activity
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);

// 返回时,在SecondActivity中调用
@Override
public void onBackPressed() {
    super.onBackPressed();
    overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right);
}

这里有个坑,我踩过好几次——overridePendingTransition必须在startActivityfinish之后立即调用,不能延迟。否则动画就不生效了。你想想看,系统在Activity切换时有一个短暂的窗口期,错过了就没了。

动画资源文件一般放在res/anim/目录下,比如这样:

<!-- slide_in_right.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="100%"
        android:toXDelta="0%"
        android:duration="300"/>
</set>

我个人习惯把常用的转场动画封装成工具类,比如ActivityAnimUtils.slideInFromRight(activity),这样团队里所有人都用同一套动画,风格统一。

小技巧: 如果你想让动画更自然,可以配合android:interpolator使用。我常用@android:anim/decelerate_interpolator,让进入的Activity先快后慢,视觉上更舒服。

8.2 ActivityOptionsCompat:更灵活的转场控制

到了Android 5.0(API 21),Google推出了ActivityOptionsCompat。它比overridePendingTransition强大得多,能让你控制更多细节。

最基本的用法是这样:

Intent intent = new Intent(this, SecondActivity.class);
ActivityOptionsCompat options = ActivityOptionsCompat
    .makeCustomAnimation(this, R.anim.slide_in_right, R.anim.slide_out_left);
startActivity(intent, options.toBundle());

看起来和之前差不多?别急,它真正的威力在后面。比如你可以指定启动时的坐标和大小:

// 从某个View的位置展开新Activity
ActivityOptionsCompat options = ActivityOptionsCompat
    .makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight());
startActivity(intent, options.toBundle());

我曾经在一个图片浏览器的项目里用过这个。点击缩略图时,新Activity从缩略图的位置放大展开,效果特别棒。用户会觉得图片「飞」出来了,而不是生硬地跳转。

注意: makeScaleUpAnimation在Android 12以上已经被标记为deprecated了。Google推荐使用共享元素转场来替代。但如果你要兼容老版本,它仍然是个好选择。

8.3 共享元素转场:真正的「无缝」体验

共享元素转场,是我个人最喜欢的View动画特性。它让两个Activity中相同的UI元素「连接」起来,用户看到的是元素在平滑移动,而不是整个页面在切换。

实现它需要三步:

  1. 在布局中标记共享元素:给两个Activity中对应的View设置相同的transitionName
  2. 启动时传递选项:使用ActivityOptionsCompat.makeSceneTransitionAnimation()
  3. 在主题中启用:确保Activity的主题使用了@style/Theme.AppCompat.Light.NoActionBar等支持转场的主题。

来看代码:

// 第一个Activity
ImageView imageView = findViewById(R.id.hero_image);
imageView.setTransitionName("hero_image");

Intent intent = new Intent(this, DetailActivity.class);
ActivityOptionsCompat options = ActivityOptionsCompat
    .makeSceneTransitionAnimation(this, imageView, "hero_image");
startActivity(intent, options.toBundle());

// 第二个Activity的布局
<ImageView
    android:id="@+id/detail_image"
    android:transitionName="hero_image"
    android:layout_width="match_parent"
    android:layout_height="300dp"/>

嗯,这里要注意:transitionName必须完全一致,大小写都不能错。我曾经因为一个下划线的问题排查了半天,最后发现是hero_imageheroImage没对上。

如果你有多个共享元素,可以这样:

ActivityOptionsCompat options = ActivityOptionsCompat
    .makeSceneTransitionAnimation(this,
        Pair.create(view1, "transition_1"),
        Pair.create(view2, "transition_2"));
避坑指南: 共享元素转场在ListView或RecyclerView中要特别小心。因为item会被复用,transitionName可能会冲突。我的做法是:在绑定数据时动态设置transitionName,比如imageView.setTransitionName("hero_" + position),确保每个item唯一。

8.4 三种方式的对比

我把它们整理成了一个表格,方便你对比选择:

特性 overridePendingTransition ActivityOptionsCompat 共享元素转场
最低API 2.0 (API 1) 5.0 (API 21) 5.0 (API 21)
动画类型 整个窗口动画 窗口动画 + 缩放/位置 元素级平滑过渡
实现复杂度
视觉效果 一般 较好 优秀
适用场景 简单跳转 需要定位展开 图片/卡片详情页

8.5 知识体系总览

下面这张图,帮你理清这三种转场方式的关系和演进路径:

Activity转场动画演进 overridePendingTransition API 1+ 窗口级动画 ActivityOptionsCompat API 21+ 更灵活的控制 共享元素 元素级过渡 核心特性对比 简单直接 XML动画资源 兼容所有版本 只能整体动画 灵活可控 支持缩放/位置 可自定义转场 API 21+ 限制 无缝体验 元素平滑过渡 支持多元素 实现较复杂 演进方向:从整体 → 局部 → 元素级

从这张图可以看得很清楚:转场动画的演进,本质上是从「整体控制」走向「元素级控制」。早期的overridePendingTransition只能对整个窗口做动画,到了ActivityOptionsCompat可以控制位置和缩放,而共享元素转场则精细到了单个UI元素。

这种思想后来被Compose继承并发扬光大——在Compose中,你甚至可以用AnimatedContentSharedTransitionLayout实现更细腻的转场效果。不过那是后话了,先把View时代的这些基础打牢,你后面学Compose时会发现很多概念是相通的。

我的建议: 如果你的项目最低API还在21以下,老老实实用overridePendingTransition。如果已经全面拥抱Android 5.0+,优先考虑共享元素转场,尤其是图片、卡片、头像这类场景。用户真的能感知到这种细节上的用心。

公众号:蓝海资料掘金营,微信deep3321