Android动画 —— Activity过渡

学如逆水行舟,不进则退。

接触Android开发虽已经颇有时日,但感觉相关知识总停留于一知半解,或者又缺乏系统关联导致顾此而失彼。是时候总结一下了。

那就从常常开发过程中经常遇到的Android动画开始吧。

Material设计为Android增加了一种动画称为Activity过渡(Activity Transitions),旨在提供不同状态下共有元素的视觉连接。官方文档的描述,听起来拗口,读起来也不够顺畅。Activity过渡在我接触的项目中没有遇到过,今天就以它初试牛刀,既是学习,也是巩固。

类别

Activity的过渡动画总共可以分为三类:

  • 进入过渡Enter transition):定义Activity中的view如何进入界面
  • 退出过渡Exit transition):定义Activity中的view如何退出界面
  • 共享元素过渡Shared elements transition): 如果两个Activity包含了若干“共享元素”(可以理解为相同的元素控件,至少,看起来是如此),那么共享元素过渡动画,就是将这些共享元素从一个Activity优雅地过渡到另一个Activity

任何继承了Visibility类的过渡,都可以作为进入或者退出过渡。目前,Android默认支持的进入/退出过渡如下表。

过渡 说明
explode 移动view进入/退出界面中央
slide 从界面的某一条边移入/移出view
fade 通过改变透明度来展现view的添加和移出

Android默认支持的共享元素过渡如下表。

过渡 说明
changeBounds 过渡view的layout bound变化
changeClipBounds 过渡view的clip bound变化
changeTransform 过渡view的scale和rotation的变化
changeImageTransform 过渡image的尺寸和放缩比例变化

过渡动画

进入/退出过渡

首先,开启过渡动画需要在style中将windowActivityTransitions属性设置为true

    <item name="android:windowActivityTransitions">true</item>

或者,也可以在代码中调用方法来设置

    getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

前面已经说过,进入、退出过渡,是指Activity中的view元素,在Activity界面显示时的动画过渡。下面,我们使用默认的Explode过渡来举个例子。

(1) SecondActivity

定义SecondActivity,用于显示进入和退出过渡,界面为一个TextView和四个定位View(用处后面会说),代码如下

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.djx.activitytransition.SecondActivity">

        <View
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:layout_margin="64dp"
            android:layout_gravity="top"
            android:background="#888" />
    
        <View
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:layout_margin="64dp"
            android:layout_gravity="top|right"
            android:background="#888" />
    
        <View
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:layout_margin="64dp"
            android:layout_gravity="bottom"
            android:background="#888" />
    
        <View
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:layout_margin="64dp"
            android:layout_gravity="bottom|right"
            android:background="#888" />
    
        <TextView
            android:id="@+id/hello"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:transitionName="test_text"
            android:textSize="45sp"
            android:text="Hello"/>
    
    </FrameLayout>

同时,在onCreate中设置并开启进入过渡,并使用内置的Explode效果。

    getWindow().setEnterTransition(new Explode());

(2) MainActivity

主界面Activity,用于启动SecondActivity,并添加transition的选项bundle

    Intent intent = new Intent(this, SecondActivity.class);
    ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this);
    startActivity(intent, options != null ? options.toBundle() : null);

(3) 过渡效果

启动SecondActivity,待界面显示出来后,点击返回键,退出。效果如下图

Enter and exit.gif

正如Explode的描述,进入时,界面中的所有View都是从外部往中间飞入(注意方向是中心);而退出时,所有View都是以中心为源而向外飞散。上面定义的文本及四个不同位置的定位View,就是为了让这个现象更清楚。

例子中都是使用代码方式控制了过渡,其实,更加简单地,可以直接在style里面设置windowEnterTransition和windowExitTransition两个属性来实现。这里就不赘述了。

值得注意的是,启动一个Activity,或者是退出覆盖其上的其他Activity,使其切回前台,都是一个“进入”过程,都会执行进入过渡;同样地,退出一个Activity,或者在其界面启动其他Activity导致其退到后台,都是一个“退出”过程,也都会执行退出过渡。而且,这两组进入与退出,是相互独立互不干扰的。

为了证实上面结论,增加一个ThirdActivity,并在SecondActivity中添加一个按键用于启动它。同时,为了进行区分,SecondActivity启动ThirdActivity的退出过渡,我们设置为另一个默认效果Slide,方向为向左。

    getWindow().setExitTransition(new Slide(Gravity.LEFT));
    startActivity(new Intent(SecondActivity.this, ThirdActivity.class),
      ActivityOptions.makeSceneTransitionAnimation(SecondActivity.this).toBundle());

启动和退出Activity顺序为

1.MainActivity - 2.SecondActivity - 3.ThirdActivity - 4.SecondActivity - 5.MainActivity

此过程的过渡效果如下

Two enters and exits.gif

可以看到,正如上述结论,SecondActivity被启动时,采用Explode效果进入,在最后退出时,也会是此效果退出(即2的进入、4的退出为一组Explode);当SecondActivity启动ThirdActivity时,退出到后台,执行了Slide效果,退出ThirdActivity使其重新回到前台时,同样执行了Slide效果进入(即2的退出、4的进入为一组Slide)。SecondActivity在整个过程中有两组进入与退出,且相互独立。

共享元素过渡

在两个Activity中,如果有两个或者多个元素控件相同(至少外表看起来如此),那么,我们就称这两个或者多个控件是共享元素。还是用例子来说明比较清晰一点。

在SecondActivity中,增加一个TextView,文本内容是“World”

<TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="World"
        android:textSize="32sp"
        android:layout_gravity="bottom|left"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:transitionName="test_text" />

同样,在ThirdActivity中,也增加一个TextView,文本内容也是“World”

<TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="World"
        android:textSize="32sp"
        android:layout_gravity="center"
        android:transitionName="test_text" />

这样一来,两个分属不同Activity的文本控件,内容相同,那么我们就认为这两个控件是共享元素。现在我们要做的,就是让这两个Activity相互跳转的时候,共享元素能够神奇地从一个Activity过渡到另一个。

(1) 文本过渡

以上面的两个TextView作过渡,十分简单

startActivity(new Intent(this, ThirdActivity.class),
      ActivityOptions.makeSceneTransitionAnimation(this, mText, "test_text").toBundle());

与进入/退出过渡类似,也调用了makeSceneTransitionAnimation方法,不过此处参数略有不同:第二个参数指定共享元素(View类型);第三个参数为过渡名称。共享元素须设置相同的名称。

无需另加效果,以上调用即可实现共享元素过渡。

Shared text.gif

是不是感觉很酷炫?

需要注意的是,这里的两个文本元素的控件属性是相同的(字体大小、颜色等),如果不同,原生的效果暂不支持,我们就会看到过渡动画过程中出现突变的问题。这个需要自定义实现,暂时留到以后再作讨论。

(2) 图片过渡

图片的共享过渡,比起文本来说,原生实现得更好,不同大小的共享图片也可以完美过渡。

类似地,分别在SecondActivity和ThirdActivity中增加一个共享ImageView。

SecondActivity中的图片

<ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        android:layout_gravity="bottom|right"
        android:src="@mipmap/ic_launcher"/>

ThirdActivity中的图片

<ImageView
        android:id="@+id/image"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_gravity="bottom"
        android:layout_marginLeft="80dp"
        android:layout_marginBottom="80dp"
        android:scaleType="centerCrop"
        android:transitionName="test_image"
        android:src="@mipmap/ic_launcher"/>

两个界面中,图片的位置和大小,都有差别。

shared image.gif

成功实现图片过渡,并且还有图片尺寸的变化。

不知道细心的你有没有发现,前面的两个过渡图片控件,并没有同时指定过渡名称(transitionName),但是依然成功的实现了过渡效果。经实验发现,只要过渡目标控件有指定过渡名称,就没有问题了。至于为什么,暂不清楚。

(3) 共享元素组过渡

如果我们需要将多个不同的共享元素同时进行过渡,也是允许的,只需要传入Pair<View, String>类型的数组即可,其中,View是共享元素,String是相应的过渡名称。

我们把前面的文本过渡、图片过渡放到一起,就是一个简单的共享元素组过渡。

options = ActivityOptions.makeSceneTransitionAnimation(this,
                  new Pair<View, String>(mImage, "test_image"), new Pair<View, String>(mText, "test_text"));
shared image and text.gif

小结

Android动画系统包含了太多有趣且实用的功能,但鉴于目前广大产品设计者和开发们没有深入挖掘,白白浪费掉了现成的好东西。Activity过渡就是其中一个。鄙人在此只作了一个简单的学习总结,抛夸引玉。如有不当和谬误之处,烦请各位看官不吝赐教啊。

附上源码,仅供参考。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容