Android中的转场动画以及material-components-android 使用


“Motion provides meaning. Objects are presented to the user without breaking the continuity of experience even as they transform and reorganize. Motion in the world of material design is used to describe spatial relationships, functionality, and intention with beauty and fluidity.”

 

上文是谷歌关于material design 交互的解释,他是material design 设计规范下一个重要部分。本文主要是介绍转场动画的一些基本使用,以及三方组件material-components-android 对于转场动画一些拓展,转场动画本质上是谷歌在转场规范下的属性动画,他提供了一些默认的转场动画效果,当然你也可以自己手动对转场动画进行定制。相关的类在 package android.transition 下,Android官方Api。(演示api并非androidx api)

 

1. TransitionManager

 

这个类主要提供了两种方式让用户去控制转场,一种方式是对一个单独的View的可见状态,大小做一个转场,在通常的情况下我们可以对xml中的viewgroup设置android:animateLayoutChanges="true",这个时候如果子view从不可见<->可见会有默认的转场动画效果。下面展示一个view从不可见到可见的一个转场效果。

findViewById(R.id.tv_vis_click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TransitionManager.beginDelayedTransition(llAnimationRoot);
                targetView.setVisibility(targetView.getVisibility() == View.VISIBLE ? View.GONE:View.VISIBLE);
            }
        });

这样一个简单的可见到不可见的转场就完成了,在默认的情况有个淡入淡出的效果。如果你需要自己定制这个转场的持续时间,转场效果时,beginDelayedTransition允许你传入自己定制transition,这里不做过多拓展。

 

上面谈到了view的可见不可见转场,除此之外我们还可以设置对于大小变换的转场。

findViewById(R.id.tv_size_click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TransitionManager.beginDelayedTransition(llAnimationRoot);
                if (targetView.getVisibility() == View.GONE){
                    targetView.setVisibility(View.VISIBLE);
                }
                //changeSize
                if (!isLarge){
                    changeSize(700);
                    isLarge = true;
                }else {
                    changeSize(300);
                    isLarge = false;
                }
            }
        });

 

合理有效的转场动画,能极大的提升用户的体验,你的app也不会再被人说成像H5一样僵硬了。TransitionManager还支持对场景的转换,比如场景A中有个view在左上角,场景B有个View在右上角,从A->B之间的转换,当然AB场景也不限于一个View。使用的时候需要注意,场景过渡的View需要做到Id相同。

layout_scene1.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv_share"
        android:layout_gravity="bottom"
        android:layout_width="wrap_content"
        android:background="@color/colorAccent"
        android:layout_height="wrap_content"
        android:text="转场Exampe"
        android:textColor="#FFFFFF"
        android:padding="20dp" />
</LinearLayout>

layout_scene2.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_share"
        android:layout_width="wrap_content"
        android:background="@color/colorAccent"
        android:layout_height="wrap_content"
        android:text="转场Exampe"
        android:textColor="#FFFFFF"
        android:padding="20dp" />


</LinearLayout>

通过layout声明了两个场景,当然你还可以通过自定义View方式声明,这里不做过多介绍

        final Scene scene1 = Scene.getSceneForLayout(llAnimationRoot, R.layout.layout_screen1, this);
        final Scene scene2 = Scene.getSceneForLayout(llAnimationRoot, R.layout.layout_screen2, this);
        findViewById(R.id.tv_scree_click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (is2Scene2){
                    ChangeBounds changeBounds = new ChangeBounds();
                    TransitionManager.go(scene1,changeBounds);
                    is2Scene2 = false;
                }else {
                    ChangeBounds changeBounds = new ChangeBounds();
                    TransitionManager.go(scene2,changeBounds);
                    is2Scene2 = true;
                }
            }
        });

 

如果你还想了解更多关于TransitionManager的使用和用法,建议移步这里,作者对TransitionManager进行了有效的自定义,查看项目你能学到更多关于TransitionManager的知识。

 

2. 页面之间的转场

 

Android页面之间的转场有个相对限制的条件,只能在ActivityA和ActivityB之间,或者在FragmentA和FragmentB之间。不允许在ActivityA和FragmentB之间进行。github上有相对更加全面的转场动画例子,例如Material-Animations。以下会对页面转场做个简单说明,同时介绍material-components-android关于转场部分的使用。

 

在Activity之间转场

在 api 21 之前android提供了过渡动画方法overridePendingTransition 在activity启动和推出的时候指定动画进行页面转换。下面我们写个简单的例子感受一下,让ActivityB从右到左进入,从左到右推出。你只需要在启动和退出的时候设置一下

 Intent intent = new Intent();
 intent.setClass(MainActivity.this,TansitionToActivity.class);
 startActivity(intent);
 overridePendingTransition(R.anim.slide_right_in,R.anim.slide_left_out);

 

    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(R.anim.slide_left_in,R.anim.slide_right_out);
    }

 

 

 

在android 5.0之后转场动画被提出,ActivityA->ActivityB可以使用transition相关api进行转场。我们需要对ActivityA 和 ActivityB 设置对应的转场动画配置,例如进入动画和退出动画,持续时间等等。

private void setupTransitionAnimations() {
        TransitionSet transitionSet = new TransitionSet();
        transitionSet.setDuration(300);
        //一起动画
        transitionSet.setOrdering(TransitionSet.ORDERING_TOGETHER);
        Slide slideTransition = new Slide();
        slideTransition.setSlideEdge(Gravity.RIGHT);
        transitionSet.addTransition(slideTransition);
        Fade fadeTransition = new Fade();
        transitionSet.addTransition(fadeTransition);
        //排除状态栏
        transitionSet.excludeTarget(android.R.id.statusBarBackground, true);
        //是否同时执行
        getWindow().setAllowEnterTransitionOverlap(false);
        getWindow().setAllowReturnTransitionOverlap(false);
        //进入
        getWindow().setEnterTransition(slideTransition);
    }

 

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setupTransitionAnimations();
        setContentView(R.layout.activity_tansition_to);

    }

调用启动新activity方法,传入bundle

  Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, null).toBundle();
                Intent intent = new Intent();
                intent.setClass(MainActivity.this,TansitionToActivity.class);
                startActivity(intent,bundle);

 如何你想手动关闭页面调用了 finish() 方法,发现转场动画不起效了,那是因为 finish() 方法并没有对转场动画做兼容,你需要调用 onBackPress() 方法或者 finishAfterTransition() 方法,这两个方法对转场做了一定的兼容处理。

 

注意ActivityOptions提供的过渡方式并非一种,由于篇幅有限,建议查看这里

 

在转场动画中,最让人惊艳的元素转场借由谷歌官方的一张图说明

 

从这张图可以看出小机器人从周围两边进行放大,而后变成了Activity2当中的大机器人。要实现这个效果,我们需要在Activity2设置入场动画,同时指定Activity1中可以被作为元素位移的View。

private void setupBondsAnimation(){
ChangeBounds changeBounds = new ChangeBounds();
changeBounds.setDuration(300);
//排除状态栏
changeBounds.excludeTarget(android.R.id.statusBarBackground, true);
changeBounds.addTarget(android.R.id.content);
//是否同时执行
getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);
//进入
getWindow().setEnterTransition(changeBounds);
}

上面的代码,指定了过渡范围在rootview。在设置布局之后还需要和Activity1当中过渡的View增加关联,这里transitionName我使用了当前的类名方便做区分。

 @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setupBondsAnimation();
        setContentView(R.layout.activity_tansition_to);
        findViewById(R.id.tv_share2).setTransitionName(TansitionToActivity.class.getName());
    }
Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, targetView,TansitionToActivity.class.getName()).toBundle();
Intent intent = new Intent();
intent.setClass(MainActivity.this,TansitionToActivity.class);
startActivity(intent,bundle);

 

 

为了让转场更加灵活,更加流畅。谷歌对Material Design现有的设计做了一定的补充(点我查看更多)。并且重新自定义了一套转场动画,和原来的使用方式相同,在动画设置上使用了自定义的模块。下面介绍几种使用,更加详情的使用示例移步这里

Container Transform

 

 

 

对比android自带的元素转场,Container Transform 在实际使用上更加流畅。本质上他自定义了元素转场的入场和出场动画,和显示计算。在Activity1当中

 

简单配置一下。

  private void setupContainerTransformInject() {
        //当Activiy2页面退出,转场动画捕捉
        setExitSharedElementCallback(new MaterialContainerTransformSharedElementCallback());
        //动画串行执行
        getWindow().setSharedElementsUseOverlay(false);
    }

设置Activity2的入场和出场动画,Activity2转场元素指定在rootview。

private void setupContainerTransformPrepare(){
        findViewById(android.R.id.content).setTransitionName(TansitionToActivity.class.getName());
        setEnterSharedElementCallback(new MaterialContainerTransformSharedElementCallback());
        MaterialContainerTransform materialContainerTransformEnter = new MaterialContainerTransform();
        materialContainerTransformEnter.addTarget(android.R.id.content);
        materialContainerTransformEnter.setDuration(1000);
        getWindow().setSharedElementEnterTransition(materialContainerTransformEnter);

        MaterialContainerTransform materialContainerTransformReturn = new MaterialContainerTransform();
        materialContainerTransformReturn.addTarget(android.R.id.content);
        materialContainerTransformReturn.setDuration(1000);
        getWindow().setSharedElementReturnTransition(materialContainerTransformReturn);


    }

上面两个方法和之间使用的一样需要配置在Activity设置布局之前。之后就是跳转一下。

 Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, targetView, TansitionToActivity.class.getName()).toBundle();
 Intent intent = new Intent();
 intent.setClass(MainActivity.this, TansitionToActivity.class);
 startActivity(intent, bundle);

 

官方文档的Shared Axis,Fade Through在最新释放的版本当中配合在activity当中使用是无效的。但是在fragment之间的转换却是正常的。emmmmmm官方的例子也没有关于Shared Axis,Fade Through之间做转场的示例代码。这个确实要吐槽一下。

在Fragment之间转场

我们在agment的replace,show等操作的时候可以对于fragment的显示做一个场景切换,使用的类型和activity一样。

 

页面转换

我们可以使用系统自带的效果,例如fade等,做界面转场,只需要在在执行切换界面的代码前设置入场或者出场的动画就行了。需要注意的一点Transition对象一次只能指定一个fragment,不可指定多个fragment,指定多个fragment也只有一个会生效!

        Fade fadea = new Fade(Visibility.MODE_IN);
        fadea.setDuration(1000);
        final FragmentA fragmentA = FragmentA.newInstance();
        fragmentA.setEnterTransition(fadea);
final FragmentB fragmentB = FragmentB.newInstance(); Fade fadeb = new Fade(Visibility.MODE_IN); fadeb.setDuration(1000); fragmentB.setEnterTransition(fadeb);
FragmentUtils.add(getSupportFragmentManager(),fragmentA,R.id.fl_content,
false); FragmentUtils.add(getSupportFragmentManager(),fragmentB,R.id.fl_content,true);
        findViewById(R.id.tv_switch).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isSwitch){
                    FragmentUtils.hide(fragmentA);
                    FragmentUtils.show(fragmentB);
                    isSwitch = false;
                }else {
                    FragmentUtils.hide(fragmentB);
                    FragmentUtils.show(fragmentA);
                    isSwitch = true;
                }
            }
        });

 

 

使用Fade Through进行转换

 

 

使用方式一样Transition替换成了Fade Through

        MaterialFadeThrough materialFadeThroughA = MaterialFadeThrough.create();
        materialFadeThroughA.setDuration(1000);
        final FragmentA fragmentA = FragmentA.newInstance();
        fragmentA.setEnterTransition(materialFadeThroughA);
final FragmentB fragmentB = FragmentB.newInstance(); MaterialFadeThrough materialFadeThroughB = MaterialFadeThrough.create(); materialFadeThroughB.setDuration(1000); fragmentB.setEnterTransition(materialFadeThroughB);
FragmentUtils.add(getSupportFragmentManager(),fragmentA,R.id.fl_content,
false); FragmentUtils.add(getSupportFragmentManager(),fragmentB,R.id.fl_content,true);

 

 

使用Shared Axis进行转换,它提供了三个方向 X Y Z,页面的所有元素都会从这三个方向进入或者是退出。

 

通过MaterialSharedAxis 的create方法生成Transition 。create方法接受两个参数,一个是方向,一个是进入或者出去的动画方向。

//MaterialSharedAxis.X
//MaterialSharedAxis.Y
//MaterialSharedAxis.Z

MaterialSharedAxis.create(MaterialSharedAxis.Z, true)

true表示是进入的动画,false表示是退出的动画。用法和上面一样 将实现的Transition替换就可。

 

 

 

 

 

 

 

posted @ 2020-05-15 17:25  MoMask  阅读(2231)  评论(0编辑  收藏  举报