前面講了屬性動(dòng)畫。這篇博客將會(huì)給大家分享一下如何使用Transition來進(jìn)行布局的更改犬第。當(dāng)然Transition也是基于屬性動(dòng)畫的。
Transition的特點(diǎn)
當(dāng)布局更改后通過Transition可以實(shí)現(xiàn)布局的過渡動(dòng)畫仓洼。
它有如下的特點(diǎn):
- Group-level animations:可以對一個(gè)View層級(jí)下的所有View應(yīng)用一個(gè)或多個(gè)動(dòng)畫效果
- Built-in animations: 可以應(yīng)用提前定義好的動(dòng)畫
- Resource file support: 可以從xml中加載內(nèi)置的動(dòng)畫
- Lifecycle callbacks: 可以接收回調(diào)實(shí)現(xiàn)動(dòng)畫生命周期的控制
使用Transition步驟
這里直接拿官方的來進(jìn)行說明:
通過這張圖可以看到宦言,整個(gè)過程由Transition Manager通過Transition將Starting Scene轉(zhuǎn)換為Ending Scene。
因此换可,這里有三部進(jìn)行Transition的使用:
- 為Starting layout椎椰、Ending layout創(chuàng)建Scene
- 創(chuàng)建定義好你想要的動(dòng)畫效果的Transition
- 調(diào)用TransitionManager.go()進(jìn)行l(wèi)ayout的切換
代碼準(zhǔn)備
創(chuàng)建TransitionActivity:
class TransitionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_transition)
if (savedInstanceState == null) {
val translation = supportFragmentManager.beginTransaction()
translation.replace(R.id.container, TransitionFragment())
translation.commit()
}
}
}
這個(gè)Activity很簡單,xml就是一個(gè)無子View的名為container的FramLayout沾鳄。這里就不列出來了慨飘。
然后是TransitionFragment的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"
android:orientation="vertical">
<RadioGroup
android:id="@+id/rg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="scene" />
<RadioButton
android:id="@+id/rb_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="true"
android:text="1" />
<RadioButton
android:id="@+id/rb_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="2" />
<RadioButton
android:id="@+id/rb_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="3" />
<RadioButton
android:id="@+id/rb_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="4" />
</RadioGroup>
<FrameLayout
android:id="@+id/scene_root"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<include layout="@layout/scene1" />
</FrameLayout>
</LinearLayout>
這里上面RaidoGroup是用來進(jìn)行各個(gè)Scene的轉(zhuǎn)換的。
下面的FramLayout是各個(gè)變換的布局的父布局译荞,它的子View將會(huì)做Transition中定義的動(dòng)畫變換瓤的。這里在FrameLayout通過inclue來引入了scene1布局。
TransitionFragment的代碼在Scene再進(jìn)行說明吞歼。
Scene的創(chuàng)建
首先圈膏,先創(chuàng)建三個(gè)scene的xml如下:
scene1.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/accent_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorAccent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/primary_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/accent_view" />
<View
android:id="@+id/dark_primary_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPrimaryDark"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/primary_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
scene2.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/accent_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorAccent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/primary_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
<View
android:id="@+id/dark_primary_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPrimaryDark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
scene3.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/accent_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorAccent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/primary_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
<View
android:id="@+id/dark_primary_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPrimaryDark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/transition_title"
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello This Transition"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
前面說明了TransitionFragment的xml,這里接著說明對應(yīng)的代碼:
class TransitionFragment : Fragment(), RadioGroup.OnCheckedChangeListener {
private lateinit var mScene1: Scene
private lateinit var mScene2: Scene
private lateinit var mScene3: Scene
private lateinit var mTransitionManagerForScene3: TransitionManager
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_transition, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//從ViewGroup對象中獲取Scene
mScene1 = Scene(scene_root, container).apply {
setEnterAction {
Toast.makeText(activity, "enter action", Toast.LENGTH_LONG).show()
}
setExitAction {
Toast.makeText(activity, "exit action", Toast.LENGTH_LONG).show()
}
}
//從layout中獲取Scene
activity?.let {
mScene2 = Scene.getSceneForLayout(scene_root, R.layout.scene2, it)
mScene3 = Scene.getSceneForLayout(scene_root, R.layout.scene3, it)
//獲取xml中的Transition
mTransitionManagerForScene3 = TransitionInflater.from(it).inflateTransitionManager(R.transition.scene3_transition_manager, scene_root)
}
//給RaidoGroup設(shè)置監(jiān)聽器
view.findViewById<RadioGroup>(R.id.rg).setOnCheckedChangeListener(this)
}
override fun onCheckedChanged(group: RadioGroup?, checkedId: Int) {
when (checkedId) {
R.id.rb_1 -> {
TransitionManager.go(mScene1)
}
R.id.rb_2 -> {
TransitionManager.go(mScene2)
}
R.id.rb_3 -> {
mTransitionManagerForScene3.transitionTo(mScene3)
}
R.id.rb_4 -> {
TransitionManager.beginDelayedTransition(scene_root)
val accentView = accent_view
val params = accentView.layoutParams
params.width = resources.getDimensionPixelSize(R.dimen.red_view_new_size)
params.height = params.width
accentView.layoutParams = params
}
}
}
}
從上面的代碼可以看到這里有兩種方式獲取Scene:
- 當(dāng)前sceneRoot下就是一個(gè)Scene的View層級(jí)的時(shí)候篙骡,可以通過Scene(sceneRoot, viewHierarchy)獲取
- 其它的Scene通過Scene.getSceneForLayout(sceneRoot, layoutId, context)從其layout中獲取
然后從Transition和TransitionManager相關(guān)的內(nèi)容在稍后進(jìn)行講解稽坤。
最后桥帆,就是給RadioGroup設(shè)置監(jiān)聽器了。
Transition的使用
在上面的TransitionFragment中看到關(guān)于Transition和TransitionManager將在這里給大家詳細(xì)的介紹了慎皱。
Transition就是在不同Scene或者是同一個(gè)Scene的布局改變實(shí)現(xiàn)動(dòng)畫過渡效果的類老虫。
-
你可以使用內(nèi)置的一些Transition類,比如:AutoTransition茫多、Fade祈匙、ChangeBounds等,更多的就請查看官方文檔了天揖。下面對一些常用的內(nèi)置Transition類進(jìn)行一下介紹:
- ChangeBounds: 當(dāng)不同的Scene的View的位置或者大小發(fā)生變化后夺欲,ChangeBounds可以對它們進(jìn)行動(dòng)畫。
- Fade: 這個(gè)Transition繼承自Visibility類今膊,F(xiàn)ade對那些出現(xiàn)些阅、消失的View進(jìn)行動(dòng)畫操作。
- AutoTransition: 這是一個(gè)默認(rèn)使用的Transition斑唬。它是一個(gè)TransitionSet市埋。它以Fade out、ChangeBounds恕刘、Fade in為順序進(jìn)行Transition動(dòng)畫操作
你還可以進(jìn)行自定義Transition缤谎。
Transition的定義和Scene的獲取是一樣的,可以通過xml或者代碼來進(jìn)行定義
在xml中定義Transition
首先褐着,創(chuàng)建res/transition/目錄
然后坷澡,新建transition xml文件到這個(gè)目錄下
最后,在xml文件添加transition
changbounds_fadein_together.xml
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds />
<fade android:fadingMode="fade_in">
<targets>
<target android:targetId="@id/transition_title" />
</targets>
</fade>
</transitionSet>
transitionSet和前面講的animatorSet是一樣的可以包含多個(gè)Transition含蓉。
在Fade Transition中可以看到設(shè)置了target频敛,target是用來指定Transition在某個(gè)對象上執(zhí)行的
在代碼中獲取Transition:
val transition = TransitionInflater.from(this).
inflateTransition(R.transition.changbounds_fadein_together)
在代碼中進(jìn)行定義
下面是上面xml中定義的Transition在代碼中對應(yīng)的例子:
val changeBounds = ChangeBounds()
val fade = Fade(Fade.IN).apply {
addTarget(R.id.transition_title)
}
val transitionSet = TransitionSet().apply {
addTransition(changeBounds)
addTransition(fade)
}
TransitionManager的使用
在上面定義好了Transition,下面就該通過TransitionManager來使用Transition了馅扣。TransitionManager仍然定義在xml和代碼中斟赚。
xml中進(jìn)行定義
scene3_transition_manager.xml
<?xml version="1.0" encoding="utf-8"?>
<transitionManager xmlns:android="http://schemas.android.com/apk/res/android">
<transition
android:toScene="@layout/scene3"
android:transition="@transition/changebounds_fadein_together" />
</transitionManager>
其中toScene表示所有Scene轉(zhuǎn)換到當(dāng)前toScene時(shí)使用changebounds_fadein_together Transition。
如果加上fromScene岂嗓,就表示從fromScene到toScene是使用changebounds_fadein_together Transition汁展。從其它的Scene跳到toScene的時(shí)候使用默認(rèn)的AutoTransition
然后通過下面的代碼獲取xml中定義的TransitionManager:
mTransitionManagerForScene3 = TransitionInflater.from(activity).
inflateTransitionManager(R.transition.scene3_transition_manager, scene_root)
代碼中定義TransitionManager
val transitionManager = TransitionManager()
transitionManager.setTransition(mScene3, changebounds_fadein_together)
使用TransitionManager進(jìn)行Scene的轉(zhuǎn)換
現(xiàn)在又回到TransitionFragment的代碼中去:
看到為RaidoGroup設(shè)置監(jiān)聽器的代碼中去。
如果你不是在xml中進(jìn)行TransitionManager的定義厌殉,那么就沒有必要實(shí)例化一個(gè)TransitionManager出來食绿。直接使用TransitionManager的go靜態(tài)方法就行。你可以不傳Transition進(jìn)去然后使用默認(rèn)AutoTransition公罕∑鹘簦或者傳入自己定義Transition。其中楼眷,rb_1铲汪、rb_2就是使用的go靜態(tài)方法熊尉。
如果你在xml中定義了TransitionManager,那么你可以想rb_3獲取TransitionManager進(jìn)行Transition動(dòng)畫過渡
rb_4中掌腰,這是不通過Scene使用Transition的情況狰住。
使用TransitionManager的beginDelayedTransition靜態(tài)方法〕萘海可以不使用Scene催植。然后在后面通過代碼對當(dāng)前布局進(jìn)行修改。對于兩個(gè)相差無幾的布局就可以不同花大力氣去定義兩個(gè)布局來通過Scene來實(shí)現(xiàn)了勺择。直接通過這樣的方式來進(jìn)行Transition的使用创南。
自定義Transition
通過繼承自Transition可以自定義Transition。
自定義Transition主要有三個(gè)方法要進(jìn)行重寫:captureStartValues省核、captureEndValues稿辙、createAnimator。
captureStartValues
這個(gè)方法用來捕獲starting Scene中所有View中我們想要捕獲的屬性气忠。這些屬性值將以鍵值對存儲(chǔ)在TransitionValues中
captureEndValues
這個(gè)方法是用來捕獲ending Scene中所有View中我們想要捕獲的屬性邻储。這些屬性值將以鍵值對存儲(chǔ)在TransitionValues中
createAnimator
當(dāng)starting Scene中的TransitionValues中的屬性值與ending Scene中TransitionValues的屬性值發(fā)生變化的時(shí)候,才會(huì)調(diào)用這個(gè)方法笔刹。在這個(gè)方法中就是定義自己的過渡動(dòng)畫了芥备。
TransitionValues
這是一個(gè)用來為一個(gè)View保存其捕獲的值冬耿、作用于它上面的Transition的數(shù)據(jù)結(jié)構(gòu)舌菜。
它里面有三個(gè)主要的屬性:
- view:用來保存這是哪個(gè)View的TransitionValues
- values:一個(gè)HashMap用來存儲(chǔ)捕獲的屬性值。其中鍵通常是以package_name:transition_name:property_name這樣的方式進(jìn)行命名的亦镶。比如:com.anriku.jetpackdemo.animation:custom_transition:scale_x日月。
- mTargetedTransitions:這個(gè)屬性是用來保存作用在當(dāng)前View上的Transition的。
現(xiàn)在通過一個(gè)例子再在進(jìn)行另外的一些內(nèi)容的介紹:
@RequiresApi(Build.VERSION_CODES.KITKAT)
class CustomTransition : Transition {
//在代碼中實(shí)例化需要的構(gòu)造器
constructor() : super()
//在xml中定義需要的構(gòu)造器
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
companion object {
private const val PROPNAME_SCALE_X =
"com.anriku.jetpackdemo.animation:custom_transition:scale_x"
private const val PROPNAME_SCALE_Y =
"com.anriku.jetpackdemo.animation:custom_transition:scale_y"
}
override fun captureStartValues(transitionValues:
androidx.transition.TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: androidx.transition.TransitionValues)
{
captureValues(transitionValues)
}
private fun captureValues(transitionValues: TransitionValues) {
val view = transitionValues.view
transitionValues.values[PROPNAME_SCALE_X] = view.width
transitionValues.values[PROPNAME_SCALE_Y] = view.height
}
override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?,
endValues: TransitionValues?): Animator? {
startValues ?: return null
endValues ?: return null
val x = (startValues.values[PROPNAME_SCALE_X] as Int).toFloat() /
endValues.values[PROPNAME_SCALE_X] as Int
val y = (startValues.values[PROPNAME_SCALE_Y] as Int).toFloat() /
endValues.values[PROPNAME_SCALE_Y] as Int
val animSet = AnimatorSet()
//Object為endValues中的view缤骨,因?yàn)檫@是將要顯示的View爱咬,就對它進(jìn)行動(dòng)畫操作
val scaleXAnim = ObjectAnimator.ofFloat(endValues.view, "scaleX", x, 1f)
val scaleYAnim = ObjectAnimator.ofFloat(endValues.view, "scaleY", y, 1f)
return animSet.apply {
duration = 1000
play(scaleXAnim).with(scaleYAnim)
}
}
}
先看到構(gòu)造器:
在代碼中進(jìn)行實(shí)例化需要無參的構(gòu)造器。在xml中定義的時(shí)候需要另外一個(gè)帶有context绊起、attrs的構(gòu)造器精拟。這個(gè)其實(shí)和自定義View有點(diǎn)像。
然后在兩個(gè)captureXXX方法中進(jìn)行了View的width虱歪、height屬性的捕獲蜂绎。
最后,進(jìn)行動(dòng)畫操作的代碼就不做詳細(xì)解釋了笋鄙。這就是講過的屬性動(dòng)畫來實(shí)現(xiàn)View的壓縮動(dòng)畫操作师枣。
自定義Transition的使用
在代碼中自定義Transition的使用和內(nèi)置的Transition是一樣的。
在xml中的使用稍有不同:
<transition class="com.anriku.jetpackdemo.animation.CustomTransition" />
這里使用transition標(biāo)簽萧落,然后在class屬性中設(shè)置自定義的Transition的類践美。
總結(jié)
在這篇博客中講解了的東西蠻清晰的洗贰。
主要是三個(gè)東西的使用:
- Scene的創(chuàng)建使用
- Transition的創(chuàng)建使用
- TransitionManager的使用