美文网首页
Android过渡动画Scene and Transition(

Android过渡动画Scene and Transition(

作者: 猫爸iYao | 来源:发表于2018-11-16 20:13 被阅读0次

    Android场景过渡——Scene and Transition(一)

    场景过渡动画

    场景过渡动画是指以动画的形式实现View场景切换,比如:Acitivty跳转动画,Fragment切换动画,或者两个View/ViewGroup之间的替换动画。相对于View Animation或Property Animator,场景过渡动画更加具有特殊性,可以看作是基于特定业务情景(场景切换)对Property Animator的高度封装。不同于Animator,场景过度动画具有特定的关注点,即如何实现具有视觉连续性的场景切换

    场景过渡动画的作用

    在Android中,一个Activity的UI通常会响应用户的输入或其他事件而作出改变。例如,一个包含搜索筛选条件表单的Activity在用户提交筛选条件时隐藏表单,并在表单位置显示搜索结果。++为了在这些情况下提供视觉连续性,我们就可以在不同的场景之间实现动画。这些动画为用户提供其交互动作的反馈,并引导他们了解应用程序如何工作。++ 换句话说,UI动画的作用不仅仅是为了视觉体验,动画强调UI变化并提供视觉引导的特点,能够帮助用户更加了解应用的功能。

    Android Transitions Framework

    从API 19(4.4.2)开始,Android提供原生Transitions Framework。利用此框架,我们能够轻松实现两个场景之间的场景过渡动画。该框架在运行时通过随时间改变View的属性值来实现动画。该框架包含一些常见效果的内建动画,并且允许我们创建自定义动画以及Transition生命周期回调。在API 21(5.0)提供了更加丰富的内建Transition效果。并且,提供API 14(4.0)及以上的andorid支持库。

    该框架具有以下功能:

    • 组级动画,允许将一个或多个动画效果运用于一个View Hierarchy(以下翻译为视图层级结构)的所有Views;
    • 过渡型动画,根据开始和结束的View的属性值变化执行动画;
    • 内建动画,该框架包含多种常见内建动画效果,例如渐变,移动;
    • 支持资源文件创建,允许通过资源文件加载View和transition animation;
    • 生命周期回调,提供回调方法以更好控制动画和View Hierarchy的变化过程。
    imageimage

    如上图所示,transition framework(蓝色部分)与view hierarchy以及animations并行工作。starting scene和ending scene分别保存starting layout 和ending layout的状态,包括它所有的views及views的属性。transition保存了一个或多个属性动画效果。要执行一个transition来实现starting layout过渡到ending layout,需要使用transitionManager,并指定ending scene和需要使用的transition。

    场景过渡动画主要有两个关键概念:Scene(场景)Transition(过渡)

    创建一个场景

    一个Scene保存了一个视图层级结构,包括它所有的views以及views的状态。Transition框架可以实现在starting scene和ending scene之间执行动画。大多数情况下,我们不需要创建starting scene,因为starting scene通常由当前UI状态决定,我们只需要创建ending scene。Transition框架允许我们通过XML文件或Java代码创建一个Scene。两种方式都需要使用Scene类。

    通过XML创建Scene

    XML创建方法主要用于静态视图,生成的场景表示创建Scene实例时视图层级结构的状态 。如果视图层级结构发生改变,则必须重新创建Scene。这种方式只能以整个layout文件的视图层级结构创建Scene,不能仅仅使用某一部分来创建。

    public static Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)
    

    从Kotlin代码中创建

    @TargetApi(21)
    public Scene(ViewGroup sceneRoot, View layout)
    
    public Scene(ViewGroup sceneRoot, ViewGroup layout)
    

    核心代码:

    1. sceneRoot:res/layout/activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/sceneRoot"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.iyao.transition.MainActivity">
        <include
            android:id="@+id/layouySceneA"
            layout="@layout/scene_a"/>
    </android.support.constraint.ConstraintLayout>
    
    1. sceneA:res/layout/scene_a.xml
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="@dimen/space_16"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        <View
            android:id="@+id/view_1"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="@drawable/shape_oval_red"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>
    </android.support.constraint.ConstraintLayout>
    
    1. sceneB:res/layout/scene_b.xml
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="@dimen/space_16"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        <View
            android:id="@+id/view_1"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="@drawable/shape_square_orange"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>
    </android.support.constraint.ConstraintLayout>
    
    1. com/iyao/transition/MainActivity.kt
     override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            //以代码的方式创建sceneA,只使用xml的一部分
            sceneA = Scene(layoutSceneRoot, layoutSceneA as ViewGroup)
            //以XML的方式创建sceneB,只能以整个XML文件创建Scene对象
            sceneB = Scene.getSceneForLayout(layoutSceneRoot, R.layout.scene_b, this)
        }
    

    创建入场/出场Action

    sceneA = Scene(layoutSceneRoot, layoutSceneA as ViewGroup).apply{
                setEnterAction { 
                //入场时调用
                }
                setExitAction { 
                //出场时调用
                }
            }
    

    注意,不要使用scene action在starting scene和ending scene的views之间传递数据,action并非场景过渡的生命周期回调方法,两个action均在transition动画执行之前被调用。

    10-27 14:39:50.164 29218-29218/com.iyao.transition I/action: sceneB exit
    10-27 14:39:50.165 29218-29218/com.iyao.transition I/action: sceneA enter
    10-27 14:39:50.176 29218-29218/com.iyao.transition I/transitionListener: sceneA transition start
    10-27 14:39:51.204 29218-29218/com.iyao.transition I/transitionListener: sceneA transition end
    

    创建并启动transition

    Transition框架中代表过渡动画的类是Transition。要想实现一个过渡动画,需要一个transition实例,一个ending scene实例。并通过TransitionManager执行过渡动画。
    Transition框架中内建了一些常见动画效果的Transition,例如:Fade(API19),ChangeBounds(API19),Slide(API21)等。所有的transitions都继承自Transition。Transition同样支持XML创建和code创建两种方式。

    从XML创建一个transition

    res/transition/fade_transition.xml

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

    Kotlin代码中获取transition实例

    val fade = TransitionInflater.from(this)
            .inflateTransition(R.transition.fade_transition)
    

    通过code创建transition

    private val fade = Fade().apply {
        //设置transition动画时长
        duration = 1000
    }
    
    

    执行transition

    Transition框架通过TransitionManager执行过度动画,需要传入一个Scene实例和一个Transition实例(可选),如果未传入Transition实例未传入,则使用默认的AutoTransition实例。

    TransitionManager.go(sceneB)
    
    TransitionManager.go(sceneB, fade)
    

    transitionSet

    transitionSet允许我们将多个transition组合使用,类似于animatorSet,使用方式如同单个transition。因为它同样继承自Transition类。

    <transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
        android:transitionOrdering="sequential">
        <fade android:fadingMode="fade_out" />
        <changeBounds />
        <fade android:fadingMode="fade_in" />
    </transitionSet>
    

    不使用scene执行一个transition

    TransitionManager类有两个静态方法

    public static void beginDelayedTransition(final ViewGroup sceneRoot)
    
    public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition)
    

    这两个方法允许我们不使用Scene类实现过渡动画,使用方法如下:

    TransitionManager.beginDelayedTransition(layoutSceneRoot, fade)
    layoutSceneRoot.removeAllViews()      
    layoutSceneRoot.addView(layoutSceneA)
    

    此方法需要传入sceneRoot和一个transition实例(可选)。调用此方法后,它会在监听到sceneRoot的视图层级结构发生的变化时,使用传入的transition实现过渡动画(如果未传入transition实例,则使用默认的AutoTransition实例)。上面的代码会在layoutSceneRoot的addView调用后,使用传入的fade实例实现过渡动画。

    transition生命周期回调

    Transition框架的TransitionListener可以让transitions实现如同Acitivty类似的生命周期回调,用以监控过渡动画执行流程。每一个transition实例都允许使用以下方法实现生命回调。

    public Transition addListener(TransitionListener listener)
    

    下面是上例中完整的Kotlin代码

    com/iyao/transition/MainActivity.kt

    package com.iyao.transition
    
    import android.os.Bundle
    import android.support.v7.app.AppCompatActivity
    import android.transition.*
    import android.util.Log
    import android.view.ViewGroup
    import kotlinx.android.synthetic.main.activity_main.*
    
    class MainActivity : AppCompatActivity() {
    
    
    
        private var sceneA : Scene? = null
        private var sceneB : Scene? = null
    
       /**
         * 当runnableSceneB使用依赖ViewOverlay实现的Transition实例(如Fade)时,sceneA持有的layoutSceneA会被
         * 添加到ViewOverlay的实际容器mOverlayViewGroup中,然后添加一个[Transition.TransitionListener](A)
         * 在[Transition.TransitionListener.onTransitionEnd]中将layoutSceneA从mOverlayViewGroup中移除。
         * 但是由于调用顺序的原因,在创建Transition实例时添加的[Transition.TransitionListener](B)的方法先于
         * A的方法执行。此时,将复用对象layoutSceneA再次添加到mSceneRoot中,就会报
         * java.lang.IllegalStateException: The specified child already has a parent. You must call
         * removeView() on the child's parent first.
         *  if (mLayoutId > 0) {
         *      LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
         *  } else {
         *      mSceneRoot.addView(mLayout);
         *  }
         *  @see Scene.enter #lineNum from 172 to 178
         *  @see Visibility.onDisappear #lineNum from 419 to 432
         */
        private val runnableSceneA: Runnable = Runnable {
            (layoutSceneA.parent as ViewGroup).removeView(layoutSceneA)
            TransitionManager.go(sceneA, changeBounds)
        }
    
        private val runnableSceneB: Runnable = Runnable {
            TransitionManager.go(sceneB, fade)
        }
    
        private val fade = Fade().apply {
            duration = 1000
            startDelay = 500
            addListener(object : TransitionListenerAdapter() {
                override fun onTransitionEnd(transition: Transition?) {
                    runnableSceneA.run()
                }
            })
        }
    
        private val changeBounds = ChangeBounds().apply {
            duration = 1000
            startDelay = 500
            addListener(object : TransitionListenerAdapter() {
                override fun onTransitionStart(transition: Transition?) {
                    Log.e("transitionListener", "sceneA transition start")
                }
                override fun onTransitionEnd(transition: Transition?) {
                    Log.e("transitionListener", "sceneA transition end")
                    runnableSceneB.run()
                }
            })
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            initialScenes()
            layoutSceneRoot.postDelayed({
                runnableSceneB.run()
            }, 1000)
        }
    
    
        private fun initialScenes() {
            @Suppress("DEPRECATION")
            sceneA = Scene(layoutSceneRoot, layoutSceneA as ViewGroup).apply{
                setEnterAction {
                    Log.e("action", "sceneA enter")
                }
            }
            sceneB = Scene.getSceneForLayout(layoutSceneRoot, R.layout.scene_b, this).apply {
                setExitAction {
                    Log.e("action", "sceneB exit")
                }
            }
        }
    
        open class TransitionListenerAdapter : Transition.TransitionListener {
            override fun onTransitionEnd(transition: Transition?) {
            }
    
            override fun onTransitionResume(transition: Transition?) {
            }
    
            override fun onTransitionPause(transition: Transition?) {
            }
    
            override fun onTransitionCancel(transition: Transition?) {
            }
    
            override fun onTransitionStart(transition: Transition?) {
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:Android过渡动画Scene and Transition(

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