MaterialDesign系列文章(三)过渡动画的实现

6,021 阅读11分钟

大家好!wo又来了。。。几天不见甚是想念!!!最近由于比较忙,请原谅我的懒惰!!!写关于MaterialDesign的文章已经有好几篇了。昨天无意间看见了一个关于过度动画的概念,所以今天想和大家分享一下关于过度动画的实战!这里唠叨一下,网上很多文章都是针对于页面跳转的动画,其实过渡动画不光可以实现页面跳转,对一些控件也是可以生效的。

如果你想直接案例解析的话,可以直接从第三条开始看!!!

先简单看一下效果:

过度动画的效果图

本文知识点:

  • 过渡动画的介绍:
  • 过渡动画类的介绍:
  • 过渡动画的按钮分析:

过度动画的介绍

关于过度动画,是在Android5.0中提出的。它通过运动和切换不同状态之间的元素来产生各种动画效果。开始的时候,没有对低版本进行适配,github上有位大神进行了相应的适类似配(使用方式) 地址如下,后来在support包中添加了对低版本的适配,所以使用的时候要注意,否则会出现警告的。。。

大体的继承关系如下:

  • TransitionManager
  • Transition
    • Visibility
      • Explode
      • Slide
      • Fade
    • ChangeBounds
    • TransitionSet
    • TextScale
    • ChangeClipBounds
    • ChangeImageTransform
    • ChangeScroll
    • ChangeTransform

过度动画类的介绍:

1.TransitionManager 过度动画管理类

这个动画管理类,主要协调相应的动画管理。重要的API有以下几个:

  • TransitionManager.beginDelayedTransition(@NonNull final ViewGroup sceneRoot, @Nullable Transition transition) 对一个根视图过渡的方法
  • TransitionManager.go(@NonNull Scene scene, @Nullable Transition transition) 对一个视图图层进行过渡的方法
  • TransitionManager.endTransitions(final ViewGroup sceneRoot) 结束所有根布局的过渡

1.1 TransitionManager.beginDelayedTransition()

这个主要是针对根试图的过渡方法:这里主要是处理一个页面中的过渡! 这个方法还有一个重载的方法:

beginDelayedTransition(@NonNull final ViewGroup sceneRoot)
beginDelayedTransition(@NonNull final ViewGroup sceneRoot, @Nullable Transition transition)
  • 参数1:根布局
  • 参数2:相应的过渡动画类型

这里的动画过渡类型有以下几类:

  • AutoTransition 默认的样式
  • Fade 淡入淡出
  • Explode 炸裂效果
  • Slide 移动

具体代码如下:

        mCl_root = findViewById(R.id.cl_root);
        mTvText = findViewById(R.id.tv_text);
//        Slide slide = new Slide(Gravity.BOTTOM);
//        Explode explode = new Explode(); 
        Fade fade = new Fade();
        TransitionManager.beginDelayedTransition(mCl_root, fade);

        if (mTvText.getVisibility() == View.VISIBLE) {
            mTvText.setVisibility(View.GONE);
        } else {
            mTvText.setVisibility(View.VISIBLE);
        }

上面会有三种效果,这里建议大家自己设置以下看看效果!

如果你想设置动画的一些其他属性的话,可以这样:

    fade.setDuration(300);//设置动画时长
    fade.setInterpolator(new FastOutSlowInInterpolator());//设置插值器
    fade.setStartDelay(200);//设置延时

上面所有都有这些属性的!!!

这里注意一点,如果你想要使用缩放、颜色、旋转变化的话,那个适配的库中有!其实就是继承了Transition这个类,自己去实现罢了!感兴趣的童鞋可以去试试。。。

1.2 TransitionManager.go()

说实话,这个东西我研究了好久,各种百度才理解了怎么用,原谅我的愚笨。。。先说说我对这个东西的理解啊!从一个布局文件过渡到另一个布局文件(注意,这里是布局文件的过渡)。我不知道怎么用语言去形容。。。

咱们看图吧!

展示效果

上面的图大家都看懂了吧!其实就是两个布局文件的过渡,中间所有的过渡都是TransitionManager帮我们封装完成的,你可以选择相应的样式进行显示!我们看看具体的代码实现吧!

首先要给大家说个类,这个类在此过度动画中起着至关重要的作用,这个类就是Scene,注释里解释为一个视图层次,也就相当于上面提到的布局文件过渡中的布局,其实这个东西你理解成布局也是可以的!

Scene getSceneForLayout(@NonNull ViewGroup sceneRoot, @LayoutRes int layoutId, @NonNull Context context)
  • 参数1:根布局(操作的视图层次所在的布局的跟标签)
  • 参数2:视图层级布局的索引
  • 参数3:根布局

这样就可以创建一个相应的视图层级了!下面我们来说代码:

因为是两个布局文件的过渡,所以这里我们需要一个总布局,两个供切换的布局,这里你可能不太明白,一会就明白了!!!相信我,不行你向我丢BUG;

  • 总布局(根布局)
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/cl_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.jinlong.newmaterialdesign.animation.TransitionManagerActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimaryDark"
        app:title="展示动画效果"
        app:titleTextColor="@android:color/white" />

    <Button
        android:id="@+id/btn_animation"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="animation"
        android:text="实现动画变换"
        app:layout_constraintTop_toBottomOf="@id/toolbar" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="scene"
        android:text="变换动画"
        app:layout_constraintLeft_toRightOf="@id/btn_animation"
        app:layout_constraintTop_toBottomOf="@id/toolbar" />

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="20dp"
        android:text="请注意这些文字的变化"
        app:layout_constraintTop_toBottomOf="@id/btn_animation" />

   <FrameLayout
       android:id="@+id/rl_root"
       android:layout_width="match_parent"
       app:layout_constraintTop_toBottomOf="@id/tv_text"
       android:layout_height="200dp">

       <include layout="@layout/scene1" />
   </FrameLayout>
</android.support.constraint.ConstraintLayout>

这里主要看FrameLayout中包裹的那个布局,这是一个布局文件!都是基于这里来的!!!

  • 布局1
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="200dp">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ_1"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="20dp"
        android:src="@mipmap/heard_1" />

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ_2"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="20dp"
        android:layout_marginRight="20dp"
        android:src="@mipmap/heard_2" />

</RelativeLayout>
  • 布局2
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="200dp">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ_2"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="20dp"
        android:src="@mipmap/heard_2" />

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ_1"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="20dp"
        android:layout_marginRight="20dp"
        android:src="@mipmap/heard_1" />

</RelativeLayout>

两个布局的展现形式是这样的。仔细的小伙伴可能看到了,两个布局的展示形式是一样的,只是两个控件的位置互换了一下。其实动画的表现形式就是那个男孩的头像从左上角移动到右下角,女孩的头像从右下角移动到左上角,正好的两个布局的展现形式。所以讲到这里我像你就会明白为什么我一直强调是布局文件的移动了吧!

  • 代码实现 初始化视图图层
    FrameLayout layout = findViewById(R.id.rl_root);
    mScene1 = Scene.getSceneForLayout(layout, R.layout.scene1, this);
    mScene2 = Scene.getSceneForLayout(layout, R.layout.scene2, this);
    TransitionManager.go(mScene1);

这里创建了两个视图层次,开始的时候,直接默认显示到视图图层1,点击按钮进行如下操作:

    TransitionManager.go(isScene2 ? mScene1 : mScene2,new ChangeBounds());
    isScene2 = !isScene2;

这里通过一个变量进行判断,如果显示的是视图图层2就切换到图层1,否则相反!就能实现上面的效果了!!!整体代码就不贴了,自己动手试一下,这样记得牢靠!其实不只局限于这种表现形式。如果三个的话,你创建三个视图图层也是可以切换的。这个你就要发挥想象空间了!

2.TransitionSet类介绍

关于这个类,可以简单理解为动画集合,可以合并多个动画一起显示出来。

    TransitionSet transitionSet = new TransitionSet();
    transitionSet.addTransition(slide);
    transitionSet.addTransition(fade);

这样就能直接把上面的过渡叠加起来了!

2.1 TransitionSet的XML使用

这里我觉得有必要说明以下xml的使用,首先要明确,这个布局的放置位置res/transition/xxx.xml。这里先贴一个整体的xml我在逐一讲解:

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="sequential"><!--是否按照顺序执行-->

    <fade android:duration="500">
        <targets>
            <target android:targetId="@id/toolBar"/>
            <target android:targetId="@android:id/statusBarBackground"/>
            <target android:targetId="@id/tv_show"/>
        </targets>
    </fade>
    
    <slide android:startDelay="600">
        <targets>
            <target android:targetId="@id/icon_sf"/>
        </targets>
    </slide>
    
    <slide android:startDelay="700">
        <targets>
            <target android:targetId="@id/icon_bh"/>
        </targets>
    </slide>

    <slide
        android:slideEdge="left"
        android:startDelay="500">
        <targets>
            <target android:targetId="@id/tv_show"/>
        </targets>
    </slide>
</transitionSet>
  • android:transitionOrdering 代表执行顺序
    • sequential 顺序执行(写在上面的先执行)
    • together 一起执行
  • fade标签 渐变的属性
  • slide标签 移动的属性
  • changeBounds标签
    • targets标签 这个里面主要是维护相应的那个控件需要过渡
      • android:excludeId 除去那个ID的控件不进行过渡(一般用于状态栏,@android:id/statusBarBackground状态栏的ID)
  • android:startDelay 延时的时间

基本上能用到的属性就这么多!!!其实这个清单文件里面主要维护的是哪个控件需要那种过渡。就酱紫!!!

讲了这么多你是不是还是一脸懵逼,其实我也是,不知道怎么去表达,所以我准备用实例去讲解,便于自己的记忆和你的理解!!!

3.过渡动画的案例分析:

终于到了本文的重点了,其实我开始学这个东西的时候也是一脸懵逼,不太理解,但是后来看到代码的时候,就不那么费劲了。。。。先从首页顶部那个图片开始说起吧!

3.1 顶部过渡动画的实现

关于上面的动画效果,主要记住共享这个概念,为什么这么说呢?因为上面这个动画是根据共享去关联的。所谓的依靠共享,主要是这个View过渡到下一个View的相应展现形式!那么就存在一个问题了,共享你必须两个View有共同的一个View,然后存在共同的ID。否则下个页面怎么知道你想要拿什么和我共享呢?这里暂时定一下Activity页面A(后面用A代替)和Activity页面B(后面用B代替)。

  • A页面的布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/cl_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.jinlong.newmaterialdesign.animation.TransitionManagerActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimaryDark"
        app:title="展示动画效果"
        app:titleTextColor="@android:color/white" />

    <LinearLayout
        android:id="@+id/ll_shared"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:onClick="sharedAnimation"
        android:orientation="horizontal"
        app:layout_constraintTop_toBottomOf="@id/toolbar">

        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/civ_heard"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@mipmap/heard_1" />

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="30dp"
            android:text="这是一个标题栏目" />
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

这里注意上面的CircleImageView控件的ID和TextView控件的ID,上面说了,后面会用到!

  • B页面的布局是这样的
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.jinlong.newmaterialdesign.animation.SharedActivity">


    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimaryDark"
        app:title="展示动画效果"
        app:titleTextColor="@android:color/white" />


    <LinearLayout
        android:id="@+id/fl_top"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintTop_toBottomOf="@id/toolbar">

        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/civ_heard"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/heard_1"
            android:transitionName="shared_image" />

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="这也是相应的标题"
            android:transitionName="shared_textview" />
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

这里的ID和上面的ID是一样的。并且注意这里面设置的transitionName标签,这个标签是设置共享内容的,马上你就会知道为什么了。。。

  • 通过代码进行相应的跳转
        CircleImageView civHead = findViewById(R.id.civ_heard);
        TextView tvTitle = findViewById(R.id.tv_title);

        Intent intent = new Intent(this, SharedActivity.class);
        ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this,
                new Pair<View, String>(civHead, "shared_image"),
                new Pair<View, String>(tvTitle, "shared_textview"));
        startActivity(intent, optionsCompat.toBundle());

3.1.1方法说明:

  • ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity, Pair<View, String>... sharedElements)
  • ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName) 这个个方法是实现共享元素平移到第二个页面的主要方法,两个方法主要区别就是上面的可以共享多个View,下面的只能共享一个View (注意这个只有在21以上的版本才有效)
  • ActivityOptionsCompat makeCustomAnimation(Context context, int enterResId, int exitResId) 这个类似于之前的overridePendingTransition的效果,传入的参数也都基本上一样
  • ActivityOptionsCompat makeScaleUpAnimation(View source, int startX, int startY, int startWidth, int startHeight) 从页面的具体那个矩形,开始放大到相应的屏幕位置,这里面的参数,基本上都是和相应的矩形有关!
  • ActivityOptionsCompat makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY) 缩略图从指定的位置扩展到全屏。用于图片的显示应该挺好的!

看了上面的内容,我相信你能写出下面的动画效果了!

虚拟机效果录得不怎么好

3.2 后面那个扩散圆形的效果

其实后面那个圆形扩散的效果是在5.0以后才有的效果(使用的是ViewAnimationUtils),所以要使用的话,必须在5.0机器上测试。否则是没有效果的!!!代码是这样的...

                    Animator circularReveal = ViewAnimationUtils.createCircularReveal(mImage_bg, 0, mImage_bg.getHeight(), 0, Math.max(mImage_bg.getHeight(), mImage_bg.getWidth()));
                    mImage_bg.setBackgroundColor(Color.BLACK);
                    circularReveal.setDuration(600);
                    circularReveal.start(); 

其实用到的只有一个API方法Animator createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius) 这里面的参数基本上就是中心点坐标,扩散半径的确定,这里就不做太多解释了!这个你可以在在21的版本试一下就可以了!

3.3 进入动画的设置

在B页面其实也是可以设置相应的入场动画的

  • getWindow().setEnterTransition(Transition transition); 设置入场时候的动画
  • getWindow().setReturnTransition(Transition transition); 设置返回时候的动画
  • getWindow().setSharedElementEnterTransition(Transition transition); 设置进入时存在共享的动画
  • getWindow().setSharedElementReturnTransition(Transition transition) 设置返回时存在共享的动画

穿插一个问题,就是Transition怎么从xml中引入

TransitionInflater.from(this).inflateTransition(R.transition.return_slide)

这里的Transition可以是一个具体的子类,也可以是一个清单文件。剩下的就是发挥大家的想象空间了!把上面的页面剩下的代码贴一下,就结束今天的内容吧!感觉篇幅有点长,感兴趣的同学,可以看看我GitHub里面的项目!!!


这篇文章我写了好几天,不知道该怎么去写,感觉写的有点乱,希望大家不要介意,有什么不懂的留言给我,我一定及时回复......今天就酱紫了!