帶你走一波Transition Animator轉場動畫相關事項(一)

動畫系列文章
帶你走一波Android自定義Animator屬性動畫相關事項(一)html

1、簡述

Transition能夠簡單理解爲一個過渡框架方便在開始場景到結束場景(不侷限於ActivityFragment等頁面跳轉過程,頁面中的控件的變化過程也是場景)設置轉場動畫(例如,淡入/淡出視圖或更改視圖尺寸)的一個API。 在Andorid 4.4.2引入的Transition框架,Andorid 5.0以上的版本跳轉過渡則創建在該功能上。java

2、關鍵概念

有兩個關鍵概念:場景scene跟轉場transitionandroid

  • scene:定義給定應用程序的UI。
  • transition:定義兩個場景之間的動態變化。

scene開始時,Transition有兩個主要職責:   git

  1. 在開始和結束的scene捕捉每一個視圖的狀態。
  2. 建立一個Animator根據視圖,將動畫的差別從一個場景到另外一個。

官方示意圖.png

3、關鍵類TransitionManager

SceneTransition聯繫起來,提供了幾個設置場景跟轉場的設置方法。github

image.png

4、Transition相關內容

系統內置transition.png

系統有實現了部分的轉場動畫的類,本身根據需求去處理,我這裏就簡單演示一下里面的幾個類,其它的你們本身去試試api

1.transition的建立

1.1. 使用佈局的方式:在res下建立transition目錄,接着建立.xml文件bash

建立單一轉場效果res/transition/slide_transition.xmlapp

<slide  xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="500"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:slideEdge="top" />
複製代碼

建立 多轉場res/transition/mulity_transition框架

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="together">
    <explode
        android:duration="1000"
        android:interpolator="@android:interpolator/accelerate_decelerate" />
    <fade
        android:duration="1000"
        android:fadingMode="fade_in_out"
        android:interpolator="@android:interpolator/accelerate_decelerate" />
    <slide
        android:duration="500"
        android:interpolator="@android:interpolator/accelerate_decelerate"
        android:slideEdge="top" />
</transitionSet>
複製代碼

載入.xml文件(多轉場跟單一轉場都是使用同一方法)ide

val transition =TransitionInflater.from(this).inflateTransition(R.transition.fade_transition)
複製代碼

1.2. 使用代碼建立translation的方式

//------------------------------- 建立單一轉場效果
val translation =  Slide().apply { 
       duration = 500
       interpolator = AccelerateDecelerateInterpolator()
       slideEdge = Gravity.TOP
}

//------------------------------- 建立多轉場效果
val transitionSet = TransitionSet()
                transitionSet.addTransition(Fade())
                transitionSet.addTransition(Slide())
                transitionSet.setOrdering(ORDERING_TOGETHER)
複製代碼

2. 使用&經常使用API

  • 基本使用
//root_view是本佈局中的最底層的佈局,本身能夠指定 可是要包含將要進行動畫的控件
//單轉場
TransitionManager.beginDelayedTransition(root_view, translation) 
toggleVisibility(view_text,view_blue, view1_red, view_yellow)

//多轉場
TransitionManager.beginDelayedTransition(root_view, transitionSet) //多轉場
toggleVisibility(view_text,view_blue, view1_red, view_yellow)
    /**
     * 四個有顏色的方塊的隱藏跟顯示
     */
private fun toggleVisibility(vararg views: View?) {
  for (view in views) {
   view!!.visibility =
      if (view!!.visibility == View.VISIBLE) View.INVISIBLE else View.VISIBLE
   }
}
複製代碼

效果圖:

gifeditor_20191218_165504.gif

這裏你能夠略清楚轉場動畫的用意,就是你指定兩個場景 好比例子中的開始是View都顯示,第二個場景是View都隱藏,設置的transitionSet或者translation就是用於中間變化的過程使用的動畫。實際上也是裏面使用了屬性動畫進行處理的。(下面自定義轉場動畫的時候會說到)

//點擊按鈕
R.id.btn_change_bounds -> {
  TransitionManager.beginDelayedTransition(root_view, ChangeBounds())
  var lp = view1_red.layoutParams
  if (lp.height == 500) {
    lp.height = 200
    } else {
    lp.height = 500
    }
  view1_red.layoutParams = lp
}
//紅框剪切的
R.id.btn_change_clip_bounds -> {
  TransitionManager.beginDelayedTransition(root_view, ChangeClipBounds())
  val r = Rect(20, 20, 100, 100)
  if (r == view1_red.clipBounds) {
    view1_red.clipBounds = null
  } else {
    view1_red.clipBounds = r
  }
}
// 藍色方塊中的字內部滑動
R.id.btn_change_scroll -> {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    val t = ChangeScroll()
    TransitionManager.beginDelayedTransition(root_view, t)
  }
  if(view_text.scrollX == -50 && view_text.scrollY == -50){
    view_text.scrollTo(0,0)
  }else{
    view_text.scrollTo(-50,-50)
  }
}
複製代碼

gifeditor_20191220_180102.gif

3. translation.Targets

配置Transition能夠給一些特殊目標的View或者去掉目標View指定Transitions.

增長動畫目標addTarget(View target)
addTarget(int targetViewId)
addTarget(String targetName) : 與 TransitionManager.setTransitionName方法設定的標識符相對應。
addTarget(Class targetType) : 類的類型 ,好比android.widget.TextView.class
移除動畫目標
removeTarget(View target)
removeTarget(int targetId)
removeTarget(String targetName)
removeTarget(Class target)
排除不進行動畫的view
excludeTarget(View target, boolean exclude)
excludeTarget(int targetId, boolean exclude)
excludeTarget(Class type, boolean exclude)
excludeTarget(Class type, boolean exclude)
排除某個 ViewGroup 的全部子View
excludeChildren(View target, boolean exclude) excludeChildren(int targetId, boolean exclude)
excludeChildren(Class type, boolean exclude)

4. 自定義 Transition動畫

主要三個方法,跟屬性定義。

  1. 屬性定義:官網提醒咱們避免跟其餘的屬性名同名,建議咱們命名規則:package_name:transition_class:property_name

  2. 三個方法:captureStartValues()captureEndValues()createAnimator()

  • captureStartValues(transitionValues: TransitionValues) 開始場景會屢次調用,在這裏你調用transitionValues.values[你定義的屬性名]並將此時屬性的值賦值給它
  • captureEndValues(transitionValues: TransitionValues) 結束場景會屢次調用,在這裏你調用transitionValues.values[你定義的屬性名]並將此時屬性的值賦值給它
  • createAnimator( sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues? ): Animator? 重點是這個函數,咱們在這裏根據開始的場景跟結束的場景值,定義對應的屬性動畫,並經過監聽屬性動畫addUpdateListener的方法,進行對應的屬性改變。
  1. 補充說明:captureStartValues()captureEndValues()其實是用於將此時的改變的屬性值,存儲到TransitionValues中的hashMap中(定義的屬性名爲key屬性值爲對應的value),方便咱們在後面createAnimator根據存儲的值進行屬性動畫的建立。
  • 例子:自定義背景顏色屬性轉場動畫
/**
 * Create by ldr
 * on 2019/12/23 16:02.
 */
class ChangeColorTransition : Transition() {

    companion object {
        /**
         *  根據官網提供的命名規則 package_name:transition_class:property_name,避免跟與其餘 TransitionValues 鍵起衝突
         *  將顏色值存儲在TransitionValues對象中的鍵
         */
        private const val PROPNAME_BACKGROUND = "com.mzs.myapplication:transition_colors:background"
    }

    /**
     * 添加背景Drawable的屬性值到目標的TransitionsValues.value映射
     */
    private fun captureValues(transitionValues: TransitionValues?) {
        val view = transitionValues?.view ?: return
        //保存背景的值,供後面使用
        transitionValues.values[PROPNAME_BACKGROUND] = (view.background as ColorDrawable).color
    }
    //關鍵方法一 :捕獲開始的場景值,屢次調用
    override fun captureStartValues(transitionValues: TransitionValues) {
        if (transitionValues.view.background is ColorDrawable)
            captureValues(transitionValues)
    }
   //關鍵方法二 :捕獲結束的場景值,屢次調用。
   // 將場景中的屬性值存儲到transitionValues的
    override fun captureEndValues(transitionValues: TransitionValues) {
        if (transitionValues.view.background is ColorDrawable)
            captureValues(transitionValues)
    }

 //關鍵方法三:根據    override fun createAnimator(
        sceneRoot: ViewGroup?,
        startValues: TransitionValues?,
        endValues: TransitionValues?
    ): Animator? {
        //存儲一個方便的開始和結束參考目標。
        val view = endValues!!.view
        //存儲對象包含背景屬性爲開始和結束佈局
        var startBackground = startValues!!.values[PROPNAME_BACKGROUND]
        var endBackground = endValues!!.values[PROPNAME_BACKGROUND]
        //若是沒有背景等的直接忽略掉
        if (startBackground != endBackground) {
          //定義屬性動畫。
            var animator = ValueAnimator.ofObject(ArgbEvaluator(), startBackground, endBackground)
        //設置監聽更新屬性
            animator.addUpdateListener { animation ->
                var value = animation?.animatedValue
                if (null != value) {
                    view.setBackgroundColor(value as Int)
                }
            }
            return animator
        }
        return null
    }
}
複製代碼

代碼中使用

var changeColorTransition = ChangeColorTransition()
changeColorTransition.duration = 2000
TransitionManager.beginDelayedTransition(root_view, changeColorTransition)
val backDrawable = view1_red.background as ColorDrawable
if (backDrawable.color == Color.RED) {
  view1_red.setBackgroundColor(Color.BLUE)
} else {
  view1_red.setBackgroundColor(Color.RED)
}
複製代碼

gifeditor_20191224_112303.gif

5、Scene的相關內容

1.Scene的建立

sceneRoot是要進行場景變化的根佈局,不用非得是整個佈局的根佈局,只要是包含了場景變化的根佈局能夠了。 R.layout.scene_layout0R.layout.scene_layout1中的要進行轉場動畫的控件id一致

  • 經過 Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) 方法。
var scene0 = Scene.getSceneForLayout(sceneRoot,R.layout.scene_layout0,this)
var scene1 = Scene.getSceneForLayout(sceneRoot,R.layout.scene_layout1,this)
複製代碼
  • 經過Scene()構造函數
val view =  LayoutInflater.from(this).inflate(R.layout.scene_layout0,sceneRoot,false)
val scene0 = Scene(sceneRoot,view)
val view1 =  LayoutInflater.from(this).inflate(R.layout.scene_layout1,sceneRoot,false)
val scene1 = Scene(sceneRoot,view1)
複製代碼

這裏有一點須要注意:LayoutInflater.from(this).inflate(R.layout.scene_layout0,sceneRoot,false),最後一個參數要傳false,否則一旦你的view添加到sceneRoot中,你去調用TransitionManager.go()傳入參數就會報錯 IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

----------------------------scene_layout0的佈局----------------------------------
<?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="match_parent"
    android:orientation="vertical">


    <ImageView
        android:id="@+id/black_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_marginStart="18dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="48dp"
        android:src="@drawable/shape_black_circle" />

    <ImageView
        android:id="@+id/yellow_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="48dp"
        android:layout_marginEnd="40dp"
        android:layout_marginRight="10dp"
        android:src="@drawable/shape_yellow_circle" />

    <ImageView
        android:id="@+id/red_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_below="@+id/black_circle"
        android:layout_alignParentStart="true"
        android:layout_marginStart="13dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="39dp"
        android:src="@drawable/shape_red_circle" />

    <ImageView
        android:id="@+id/blue_circle"
        android:layout_width="160dp"
        android:layout_height="160dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="241dp"
        android:layout_marginEnd="45dp"
        android:layout_marginRight="10dp"
        android:src="@drawable/shape_blue_circle" />
</RelativeLayout>

----------------------------scene_layout1的佈局----------------------------------
與scene_layout0同樣,只是ImageView的位置更換了一下。
複製代碼

兩個場景要進行轉場變化的控件id是一致的。

我經過實踐發現了一個問題:當多個轉場控件放到不一樣的ViewGroup下面,而不是在同一個ViewGroup的佈局下面,產生的動畫會有不一致的狀況。

上面的全部ImageView都放在RelativeLayout的佈局下面,與使用Linearlayout爲縱向根佈局再加上兩個子橫向Linearlayout,再將ImageView兩兩放置到子橫向Linearlayout中,你會看到位置變化的轉場效果可能不是你所指望的。(這裏應該是由於不在同一個ViewGroup下致使的)

2. 使用

//場景1:
val transition = TransitionInflater.from(this).inflateTransition(R.transition.explore_transtion)
TransitionManager.go(scene0,transition)

//場景2:
val transition = TransitionInflater.from(this).inflateTransition(R.transition.explore_transtion)
TransitionManager.go(scene1,transition)

複製代碼

gifeditor_20191225_100316.gif

6、Activity間的轉場動畫

圖解.png

1. 基本主要API

  • window.enterTransition: 進入時候的轉場效果
  • window.exitTransition: 退出時候的轉場效果
  • window.reenterTransition: 從新進入的轉場效果
  • window.returnTransition: 回退的時候的轉場效果

對應樣式下的

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
  <item name="android:windowEnterTransition"></item>
  <item name="android:windowExitTransition"></item>
  <item name="android:windowReenterTransition"></item>
  <item name="android:windowReturnTransition"></item>
 </style>
複製代碼

2. Android 支持如下進入和退出過渡:

explore : 將視圖移入場景中心或從中移出。
slide : 將視圖從場景的其中一個邊緣移入或移出。
fade : 經過更改視圖的不透明度,在場景中添加視圖或從中移除視圖。
系統支持將任何擴展 Visibility 類的過渡做爲進入或退出過渡。

3. 基本使用

onCreate()中設置轉場動畫

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setUpWindow()
    }
    private fun setUpWindow() {
       window.let {
            it.exitTransition = TransitionInflater.from(this).inflateTransition(R.transition.fade_transtion)
            it.enterTransition = Explode().apply {
                duration = 500
            }
            it.reenterTransition = Explode().apply {
                duration = 500
            }
            it.returnTransition = Slide().apply {
                duration = 500
            }
        }
        }
    }
複製代碼

跳轉的時候,startActivity增長bundle

val intent = Intent(this@MainActivity, SampleTranslateActivity::class.java)
val bundle =  ActivityOptionsCompat.makeSceneTransitionAnimation(this).toBundle()//Androidx提供的類
//val bundle =  ActivityOptions.makeSceneTransitionAnimation(this).toBundle()//不是Andoridx的時候使用ActivityOptions
startActivity(intent,bundle)
複製代碼

gifeditor_20191226_111126.gif
上面的效果存在一些問題,有些動畫重疊在一塊了。 咱們須要設置一下代碼讓進入退出的動畫按序完成而不重疊到一塊的時候,

setWindowAllowEnterTransitionOverlap(false)
setWindowAllowReturnTransitionOverlap(false)
複製代碼

或者在Activity或者Application對應的樣式下面增長

<item name="android:windowAllowEnterTransitionOverlap">false</item>
<item name="android:windowAllowReturnTransitionOverlap">false</item>
複製代碼

gifeditor_20191226_112449.gif

7、Activity間的共享轉場動畫

image.png

1.基本API

對應各方法進入時候的轉場效果,跟上面的轉場動畫的api是相對的

  • window.sharedElementEnterTransition
  • window.sharedElementExitTransition
  • window.sharedElementReenterTransition
  • window.sharedElementReturnTransition 對應樣式下的
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:windowSharedElementEnterTransition"></item>
        <item name="android:windowSharedElementExitTransition"></item>
        <item name="android:windowSharedElementReenterTransition"></item>
        <item name="android:windowSharedElementReturnTransition"></item>
   </style>
複製代碼

2.基本使用

注意:版本要大於android5.0以上的,纔有提供共享元素場景動畫,用的時候記得作一下版本兼容

// Check if we're running on Android 5.0 or higher if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Apply activity transition } else { // Swap without transition } 複製代碼

2.1. 先在xml樣式中開啓

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
 <item name="android:windowContentTransitions">true</item>
</style>
複製代碼

或者代碼中開啓

requestWindowFeature(Window.FEATURE_CONTENT_TRANSITIONS)
複製代碼

2.2. 定義兩個佈局都要設置android:transitionName 跳轉佈局一

<ImageView
        android:id="@+id/image_blue"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:src="@drawable/shape_blue_circle"
        android:transitionName="blue_name"
        />
    <TextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:transitionName="textName"
        android:text="這個是我等下轉場僞裝變大的數據~~~~"
        />
複製代碼

佈局二

<ImageView
        android:id="@+id/imageView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginTop="68dp"
        android:src="@drawable/shape_blue_circle"
        android:transitionName="blue_name"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="68dp"
        android:text="TextView"
        android:transitionName="textName"
        android:textSize="23sp"
        android:textColor="@color/black"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
複製代碼

2.3 在兩個Activity中分別設置共享元素的轉場動畫

window.sharedElementEnterTransition = ChangeBounds()
window.sharedElementExitTransition = ChangeBounds()
複製代碼

2.3 跳轉開始

val intent = Intent(this@MainActivity, ShareElementActivity2::class.java)
// 構造多個Pair 一個Pair對應一個共享元素 
val pair = Pair(image_blue as View, image_blue.transitionName)
val pair1 = Pair(text1 as View, text1.transitionName)
// 將多個共享元素傳入
val options = ActivityOptions.makeSceneTransitionAnimation(
  this@MainActivity,
  pair, pair1
)
startActivity(intent, options.toBundle())
複製代碼

gifeditor_20191226_142635.gif

限制(選自Android官方文檔)

  • Android 版本在 4.0(API Level 14)4.4.2(API Level 19) 使用 Android Support Library’s

  • 應用於 SurfaceView 的動畫可能沒法正確顯示。 SurfaceView 實例是從非界面線程更新的,所以這些更新與其餘視圖的動畫可能不一樣步。

  • 當應用於 TextureView 時,某些特定過渡類型可能沒法產生所需的動畫效果。

  • 擴展 AdapterView 的類(例如 ListView)會以與過渡框架不兼容的方式管理它們的子視圖。若是您嘗試爲基於 AdapterView 的視圖添加動畫效果,則設備顯示屏可能會掛起。

  • 若是您嘗試使用動畫調整 TextView 的大小,則文本會在該對象徹底調整過大小以前彈出到新位置。爲了不出現此問題,請勿爲調整包含文本的視圖的大小添加動畫效果。

本章的源碼:

github.com/lovebluedan…

感謝:

Android官方文檔
github.com/lgvalle/Mat…
github.com/codepath/an…

相關文章
相關標籤/搜索