動畫系列文章
帶你走一波Android自定義Animator屬性動畫相關事項(一)html
Transition
能夠簡單理解爲一個過渡框架方便在開始場景到結束場景(不侷限於Activity
跟Fragment
等頁面跳轉過程,頁面中的控件的變化過程也是場景)設置轉場動畫(例如,淡入/淡出視圖或更改視圖尺寸)的一個API。 在Andorid 4.4.2
引入的Transition
框架,Andorid 5.0
以上的版本跳轉過渡則創建在該功能上。java
有兩個關鍵概念:場景scene
跟轉場transition
。android
scene
:定義給定應用程序的UI。transition
:定義兩個場景之間的動態變化。當
scene
開始時,Transition
有兩個主要職責: git
- 在開始和結束的
scene
捕捉每一個視圖的狀態。- 建立一個
Animator
根據視圖,將動畫的差別從一個場景到另外一個。
將
Scene
和Transition
聯繫起來,提供了幾個設置場景跟轉場的設置方法。github
系統有實現了部分的轉場動畫的類,本身根據需求去處理,我這裏就簡單演示一下里面的幾個類,其它的你們本身去試試api
transition
的建立1.1. 使用佈局的方式:在res
下建立transition
目錄,接着建立.xml
文件bash
建立單一轉場效果res/transition/slide_transition.xml
app
<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)
複製代碼
//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
}
}
複製代碼
效果圖:
這裏你能夠略清楚轉場動畫的用意,就是你指定兩個場景 好比例子中的開始是
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)
}
}
複製代碼
配置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)
主要三個方法,跟屬性定義。
屬性定義:官網提醒咱們避免跟其餘的屬性名同名,建議咱們命名規則:
package_name:transition_class:property_name
三個方法:
captureStartValues()
、captureEndValues()
、createAnimator()
captureStartValues(transitionValues: TransitionValues)
開始場景會屢次調用,在這裏你調用transitionValues.values[你定義的屬性名]
並將此時屬性的值賦值給它captureEndValues(transitionValues: TransitionValues)
結束場景會屢次調用,在這裏你調用transitionValues.values[你定義的屬性名]
並將此時屬性的值賦值給它createAnimator( sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues? ): Animator?
重點是這個函數,咱們在這裏根據開始的場景跟結束的場景值,定義對應的屬性動畫,並經過監聽屬性動畫addUpdateListener
的方法,進行對應的屬性改變。
- 補充說明:
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)
}
複製代碼
Scene
的建立
sceneRoot
是要進行場景變化的根佈局,不用非得是整個佈局的根佈局,只要是包含了場景變化的根佈局能夠了。R.layout.scene_layout0
與R.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
下致使的)
//場景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)
複製代碼
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>
複製代碼
explore
: 將視圖移入場景中心或從中移出。
slide
: 將視圖從場景的其中一個邊緣移入或移出。
fade
: 經過更改視圖的不透明度,在場景中添加視圖或從中移除視圖。
系統支持將任何擴展 Visibility
類的過渡做爲進入或退出過渡。
在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)
複製代碼
上面的效果存在一些問題,有些動畫重疊在一塊了。 咱們須要設置一下代碼讓進入退出的動畫按序完成而不重疊到一塊的時候,
setWindowAllowEnterTransitionOverlap(false)
setWindowAllowReturnTransitionOverlap(false)
複製代碼
或者在Activity
或者Application
對應的樣式下面增長
<item name="android:windowAllowEnterTransitionOverlap">false</item>
<item name="android:windowAllowReturnTransitionOverlap">false</item>
複製代碼
對應各方法進入時候的轉場效果,跟上面的轉場動畫的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>
複製代碼
注意:版本要大於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())
複製代碼
Android
版本在 4.0(API Level 14)
到4.4.2(API Level 19)
使用 Android Support Library’s
應用於 SurfaceView
的動畫可能沒法正確顯示。 SurfaceView
實例是從非界面線程更新的,所以這些更新與其餘視圖的動畫可能不一樣步。
當應用於 TextureView
時,某些特定過渡類型可能沒法產生所需的動畫效果。
擴展 AdapterView
的類(例如 ListView
)會以與過渡框架不兼容的方式管理它們的子視圖。若是您嘗試爲基於 AdapterView
的視圖添加動畫效果,則設備顯示屏可能會掛起。
若是您嘗試使用動畫調整 TextView
的大小,則文本會在該對象徹底調整過大小以前彈出到新位置。爲了不出現此問題,請勿爲調整包含文本的視圖的大小添加動畫效果。