Android动画<第八篇>:转场动画与共享元素

我在第七篇Android动画<第七篇>:Activity切换动画已经实现了Activity的切换动画,在文章的最后,我留了一个悬念,即使用ActivityOptions来实现Activity的过度动画。

当跳转Activity时,常常使用如下代码实现

                startActivity(intent);

这个代码是我们常见的了,但是跳转Activity还有另一个方法

public void startActivity(Intent intent, @Nullable Bundle options)

这个带有Bundle参数的方法可以实现Activity的过度动画,第二个参数可以传递

ActivityOptions.makeSceneTransitionAnimation(this).toBundle()

但是,这个方法从Android5.0之后才推出,当我们做跳转动作时需要判断版本号,如下:

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
            }else{
                startActivity(intent);
            }

仅仅使用ActivityOptions是看不到动画的,需要配合Transition才能看到动画效果。

Activity之间的切换(过渡),可以看成场景和场景之间的切换(过渡),在切换(过渡)过程中可以添加Transition实现变换操作。

makeSceneTransitionAnimation字面上的意思可以看出:

make:制作
Scene:场景
Transition:过渡、切换、变换
Animation:动画

连起来的意思是:制作场景过渡动画,我把场景和场景之间的过渡动画称之为转场动画

makeSceneTransitionAnimation的第二个参数是一个可变长度的数组,源码如下:

@SafeVarargs
public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
        Pair<View, String>... sharedElements) {
    ActivityOptions opts = new ActivityOptions();
    makeSceneTransitionAnimation(activity, activity.getWindow(), opts,
            activity.mExitTransitionListener, sharedElements);
    return opts;
}

sharedElements参数称之为共享元素

转场动画共享元素是本章的重点。

(1)转场动画

首先,设置下主题:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>

    <!--允许使用transitions -->
    <item name="android:windowContentTransitions">true</item>
    
    <!--true:同时执行,false:顺序执行-->
    <item name="android:windowAllowEnterTransitionOverlap">false</item>
    <item name="android:windowAllowReturnTransitionOverlap">false</item>
</style>

</resources>

windowContentTransitions默认为true,意思是:是否允许使用transitions 。(不过这个属性应该没有作用了,我设置成false,transitions 依然可以使用)
windowAllowEnterTransitionOverlap默认为true,true为创建场景时,前后两个场景动画同时执行。false为创建场景时,前后两个场景动画顺序执行。
windowAllowReturnTransitionOverlap默认为true,true为销毁场景时,前后两个场景动画同时执行。false为销毁场景时,前后两个场景动画顺序执行。

想要实现动画,还需要用到Transition对象,Transition的完整包名是android.transition.Transition,是Android SDK自带的接口。

Transition有三个间接子类,分别是:Explode动画Fade动画Slide动画

根据源码注释,Explode和Slide都是移入移出的意思,Fade是淡入淡出的意思。

那么,这三种动画该怎么去实现呢?需要结合ActivityOptionsmakeSceneTransitionAnimation方法。

N Slide动画 Explode动画 Fade动画
效果展示
167.gif
168.gif
169.gif
详细说明 Slide动画从边缘开始移入移出动画,默认从下方开始显示,可以用代码控制的方向有:左、上、右、下 Explode动画从中间开始移入移出动画,它没有方向 Fade动画官方注释给出的解释是淡入淡出的意思




[Slide代码]

跳转到SceneTransitionActivity界面:

            Intent intent = new Intent(MainActivity.this, SceneTransitionActivity.class);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this).toBundle());
            }else{
                startActivity(intent);
            }

SceneTransitionActivity中需要设置:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Slide slide = new Slide();
        slide.setDuration(1000);
        slide.setSlideEdge(Gravity.LEFT);
        //创建Activity时的动画
        getWindow().setEnterTransition(slide);
        //销毁Activity时的过渡动画
        getWindow().setReturnTransition(slide);
        //从前台切换到后台时的动画
        getWindow().setExitTransition(slide);
        //Activity从后台切换到前台的动画
        getWindow().setReenterTransition(slide);
    }

[Explode代码]

跳转到SceneTransitionActivity界面:

            Intent intent = new Intent(MainActivity.this, SceneTransitionActivity.class);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this).toBundle());
            }else{
                startActivity(intent);
            }

SceneTransitionActivity中需要设置:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Explode explode = new Explode();
        explode.setDuration(1000);
        //创建Activity时的动画
        getWindow().setEnterTransition(explode);
        //销毁Activity时的过渡动画
        getWindow().setReturnTransition(explode);
        //从前台切换到后台时的动画
        getWindow().setExitTransition(explode);
        //Activity从后台切换到前台的动画
        getWindow().setReenterTransition(explode);
    }

[Fade代码]

跳转到SceneTransitionActivity界面:

            Intent intent = new Intent(MainActivity.this, SceneTransitionActivity.class);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this).toBundle());
            }else{
                startActivity(intent);
            }

SceneTransitionActivity中需要设置:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Fade fade = new Fade();
        fade.setDuration(3000);
        //创建Activity时的动画
        getWindow().setEnterTransition(fade);
        //销毁Activity时的过渡动画
        getWindow().setReturnTransition(fade);
        //从前台切换到后台时的动画
        getWindow().setExitTransition(fade);
        //Activity从后台切换到前台的动画
        getWindow().setReenterTransition(fade);
    }

以上三种动画用Java代码炫酷的实现了Activity的转场动画,那么除了使用Java代码实现之外还可以使用style方式实现:

样式:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">

    <item name="android:windowEnterTransition">@transition/transition_explode</item>
    <item name="android:windowReturnTransition">@transition/transition_fade</item>
    <item name="android:windowExitTransition">@transition/transition_fade</item>
    <item name="android:windowReenterTransition">@transition/transition_explode</item>
</style>

布局文件:

图片.png

transition_explode.xml

<?xml version="1.0" encoding="utf-8"?>
<explode xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000" />

transition_fade.xml

<?xml version="1.0" encoding="utf-8"?>
<fade
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
/>

transition_slide.xml

<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000" />

(2)共享元素

共享元素转场动画是两个不同的概念,转场动画是使用Transition对象产生的场景开始到结束的过渡动画,而共享元素是场景开始(场景A)的View和场景结束(场景B)的View相互绑定。

那么该如何绑定两个场景的View呢?

为了防止Transition混淆演示图效果,我暂时将Transition去掉,仅仅使用共享元素完成动画。

从上面的讲解,我们知道了Activity之间的跳转其实就是场景的切换,场景的切换动画是由Transition来实现的,那么共享元素也能实现特殊的动画。

startActivity的第二个参数的ActivityOptions.makeSceneTransitionAnimation方法可以传递共享元素,代码如下:

场景A跳转代码,将场景A的一个view和一个transitionName传递到场景二

View transition_view = findViewById(R.id.image_a);
String transition_name = getString(R.string.transition_name_image);
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, transition_view, transition_name).toBundle());

当然,如果有多个共享元素,可以使用Pair对象

ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, Pair.create(transition_view, transition_name),  Pair.create(transition_view, transition_name)).toBundle()


当切换到场景二时,其中的某View也必须标注transitionName

<ImageView
    android:id="@+id/image_b"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@mipmap/ani_test"
    android:transitionName="@string/transition_name_image"
    android:layout_centerInParent="true"/>


两者相互绑定的View必须具有相同的transitionName(名字随便命名)

<string name="transition_name_image">image</string>

这样,场景A和场景B之间的元素形成了共享,这就是共享元素

效果如下:

171.gif

从效果图中,可以明显的看出,图形1和图形2之间的动画效果。

转场动画共享元素结合使用效果如下:

172.gif

我想大家都能看出,转场动画的时间和共享元素过渡动画的时间不一样,那是因为我将转场动画的时间设置为了1秒,而共享元素过渡动画的时间是系统默认。

那么,怎么调整共享元素过渡动画的时间呢?

我们可以设置style来调整共享元素的时间,代码如下:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>

    <!--允许使用transitions -->
    <item name="android:windowContentTransitions">true</item>

    <!--true:同时执行,false:顺序执行-->
    <item name="android:windowAllowEnterTransitionOverlap">false</item>
    <item name="android:windowAllowReturnTransitionOverlap">false</item>

    <!--转场动画设置-->
    <item name="android:windowEnterTransition">@transition/transition_explode</item>
    <item name="android:windowReturnTransition">@transition/transition_explode</item>
    <item name="android:windowExitTransition">@transition/transition_explode</item>
    <item name="android:windowReenterTransition">@transition/transition_explode</item>


    <!--共享元素动画设置-->
    <item name="android:windowSharedElementExitTransition">@transition/transition_set</item>
    <item name="android:windowSharedElementReenterTransition">@transition/transition_set</item>
    <item name="android:windowSharedElementEnterTransition">@transition/transition_set</item>
    <item name="android:windowSharedElementReturnTransition">@transition/transition_set</item>

</style>

其中,主要看最下面四个,共享元素动画设置

transition_set.xml

<?xml version="1.0" encoding="utf-8"?>
<transitionSet
    xmlns:android="http://schemas.android.com/apk/res/android">
    <!--为目标视图的布局边界的变化添加动画-->
    <changeBounds android:duration="2000"/>
    <!--为目标视图的裁剪边界的变化添加动画-->
    <changeTransform android:duration="2000"/>
    <!--为目标视图的缩放与旋转变化添加动画-->
    <changeClipBounds android:duration="2000"/>
    <!--为目标图像的大小与缩放变化添加动画-->
    <changeImageTransform android:duration="2000"/>
</transitionSet>

至于四个共享元素动画的配置是什么意思,看完文章上面部分,我想都能猜到。transitionSet属性下的changeBoundschangeTransformchangeClipBoundschangeImageTransform都有注释,这里不详细描述了。

最终效果如下:

173.gif

(3)Fragment之间实现过渡动画

上面介绍了Activity之间的过渡动画,Fragment之间的过渡动画其实也差不多。

当Fragment切换时,的Transition动画

        Slide slideTransition = new Slide(Gravity.LEFT);
        slideTransition.setDuration(1000);
        demo2Fragment.setEnterTransition(slideTransition);

        getSupportFragmentManager().beginTransaction()
                .replace(R.id.container, demo2Fragment)
                .commit();

效果如下:

174.gif

它的效果和Activity切换效果看起来差不多。那么共享元素 动画该怎么去实现呢?

代码如下:

从Fragment1切换到Fragment2

public void jumpToFragment2(View view){

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

        Slide slideTransition = new Slide(Gravity.LEFT);
        slideTransition.setDuration(1000);
        demo2Fragment.setEnterTransition(slideTransition);

        Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.transition_set);
        demo2Fragment.setSharedElementEnterTransition(transition);

        getSupportFragmentManager().beginTransaction()
                .replace(R.id.container, demo2Fragment)
                .addSharedElement(view, getString(R.string.transition_name_image))
                .commit();
    }else{
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.container, demo2Fragment)
                .commit();
    }
}

从Fragment2切换到Fragment1

public void jumpToFragment1(View view){

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

        Slide slideTransition = new Slide(Gravity.RIGHT);
        slideTransition.setDuration(1000);
        demo1Fragment.setEnterTransition(slideTransition);

        Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.transition_set);
        demo1Fragment.setSharedElementEnterTransition(transition);

        getSupportFragmentManager().beginTransaction()
                .replace(R.id.container, demo1Fragment)
                .addSharedElement(view, getString(R.string.transition_name_image))
                .commit();
    }else{
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.container, demo1Fragment)
                .commit();
    }
}

在fragment1中的某view中绑定共享元素名称:transitionName

<ImageView
    android:id="@+id/image_a"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:src="@mipmap/ani_test"
    android:transitionName="@string/transition_name_image"
    android:layout_centerVertical="true"/>

在fragment2中的某view中绑定共享元素名称:transitionName

<ImageView
    android:id="@+id/image_b"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@mipmap/ani_test"
    android:transitionName="@string/transition_name_image"
    android:layout_centerInParent="true"/>

我们先删除如下代码:

    Slide slideTransition = new Slide(Gravity.LEFT);
    slideTransition.setDuration(1000);
    demo2Fragment.setEnterTransition(slideTransition);

也就是删除Transition动画相关代码,效果如下:

175.gif

加上Transition动画之后的效果如下:

176.gif

最后,别忘了以下这两个方法(上面有介绍)

        demo1Fragment.setAllowEnterTransitionOverlap(false);
        demo1Fragment.setAllowReturnTransitionOverlap(false);

(4)布局间的场景动画

[第一步] 创建场景

Scene scene1 = Scene.getSceneForLayout(ll_yuan, R.layout.scene1, getActivity());

[第二步] 创建Transition

                Fade fade = new Fade(Fade.OUT);
                fade.setDuration(3000);

[第三步] 执行切换场景动画(三种方式)

        TransitionManager.go(scene1, fade);
        //TransitionManager.go(scene1, TransitionInflater.from(getActivity()).inflateTransition(R.transition.transition_set));
        //TransitionManager.go(scene1);

效果如下:

177.gif

除了多个场景之间的过渡动画之外,还可以使用更改某一个场景的属性触发属性变化的动画。

[第一步]

TransitionManager.beginDelayedTransition

[第二步] 修改view属性

代码如下:

按钮一:

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                ChangeBounds changeBounds = new ChangeBounds();
                changeBounds.setDuration(3000);
                TransitionManager.beginDelayedTransition(ll_yuan, changeBounds);
                ViewGroup.LayoutParams params = image_yuan.getLayoutParams();
                params.width = 600;
                image_yuan.setLayoutParams(params);
            }

按钮二:

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                ChangeBounds changeBounds = new ChangeBounds();
                changeBounds.setDuration(3000);
                TransitionManager.beginDelayedTransition(ll_yuan, changeBounds);
                ViewGroup.LayoutParams params = image_yuan.getLayoutParams();
                params.width = 100;
                image_yuan.setLayoutParams(params);
            }

效果如下:

178.gif

转场动画共享元素动画暂时就那么多了,其实还有很多动画效果和动画的组合都没说到,本章只是讲解基础,在后期的文章中尽量给大家补全。

[本章完...]

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

推荐阅读更多精彩内容