美文网首页动画
Android动画<第八篇>:转场动画与共享元素

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

作者: NoBugException | 来源:发表于2019-09-20 10:57 被阅读0次

    我在第七篇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

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

    [本章完...]

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/cgtctctx.html