Motion Scene Programmatically
MotionLayout: creating MotionScene without XML
--
RESULT:
In this example, you will learn how to implement MotionScene completely programmatically.
1️⃣ Creating a Layout
res/layout/activity_main
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout 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"
app:layoutDescription="@xml/activity_main_scene"
tools:context=".MainActivity">
<View
android:id="@+id/view"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/black"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent=".1" />
</androidx.constraintlayout.motion.widget.MotionLayout>
2️⃣ Necessarily -> app:layoutDescription
res/xml/activity_main_scene
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
app:constraintSetEnd="@id/end"
app:constraintSetStart="@+id/start" />
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@+id/widget" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint android:id="@id/widget" />
</ConstraintSet>
</MotionScene>
app:layoutDescription — must be present even as a placeholder.
3️⃣ Create ids file
res/values/ids
<resources>
<item name="transition_id" type="id"/>
<item name="start_set_id" type="id"/>
<item name="end_set_id" type="id"/>
</resources>
4️⃣ Preparing the starting and final states
I use viewBinding:
bild.gradle(Module: …)
android {
buildFeatures {
viewBinding = true
}
}
MainActivity.class
// Start
private fun ConstraintLayout.initStartSet() = ConstraintSet().apply {
clone(this@initStartSet)
applyTo(this@initStartSet)
}
// End
private fun ConstraintLayout.initEndSet() = ConstraintSet().apply {
clone(this@initEndSet)
constrainPercentWidth(binding.view.id, .5f)
connect(binding.view.id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END)
connect(binding.view.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
applyTo(this@initEndSet)
}
In this example, I am sure that the binding will be initialized, if your situation is different, you can pass views to the function arguments.
5️⃣ Create MotionScene
private lateinit var scene: MotionScene
To further add and change Transitions to it and ConstraintSets.
6️⃣ Initialize and Launch-> MotionScene
Initialize and Lounch MotionScene when clicking on parent ViewGroup.
FIRST method:
binding.root.setOnClickListener {
scene = MotionScene(binding.root).apply {
val transition = TransitionBuilder.buildTransition(this, R.id.transition_id, R.id.start_set_id, binding.root.initStartSet(), R.id.end_set_id, binding.root.initEndSet())
setTransition(transition)
binding.root.setScene(this)
}binding.root.apply {
setTransitionDuration(1000)
transitionToEnd()
}
SECOND method:
scene = MotionScene(binding.root).apply {
val transition = TransitionBuilder.buildTransition(this, R.id.transition_id, R.id.start_set_id, ConstraintSet(), R.id.end_set_id, ConstraintSet())
setTransition(transition)
binding.root.setScene(this)
}
binding.root.apply {
getConstraintSet(R.id.start_set_id).clone(initStartSet())
getConstraintSet(R.id.end_set_id).clone(initEndSet())
setTransitionDuration(1000)
transitionToEnd()
}
We could end on this, but there is one more thing:
7️⃣ The Interesting 😋😉🤩
8️⃣ Add id for next transition
res/values/ids
<resources>
<item name="transition_id" type="id"/>
<item name="start_set_id" type="id"/>
<item name="end_set_id" type="id"/>
<item name="transition_rotation_id" type="id"/>
<item name="rotation_set_id" type="id"/>
</resources>
9️⃣ Preparing the state
// Rotation
private fun ConstraintLayout.initRotationSet() = ConstraintSet().apply {
clone(this@initRotationSet)
setRotation(binding.view.id, 360f)
applyTo(this@initRotationSet)
}
🔟 Creating a field for counting clicks
When pressed for the first time, the transition from the start to the end state;
When pressed for the second time, the transition from the end to the rotation state;
private var clickCounter = 0binding.root.setOnClickListener {
when (clickCounter) {
0 -> {
scene = MotionScene(binding.root).apply {
val transition = TransitionBuilder.buildTransition(this, R.id.transition_id, R.id.start_set_id, binding.root.initStartSet(), R.id.end_set_id, binding.root.initEndSet())
setTransition(transition)
binding.root.setScene(this)
}
binding.root.apply {
setTransitionDuration(1000)
transitionToEnd()
}
clickCounter++
}
1 -> {
scene.apply {
val transition = TransitionBuilder.buildTransition(this, R.id.transition_rotation_id, R.id.end_set_id, binding.root.initEndSet(), R.id.rotation_set_id, binding.root.initRotationSet())
addTransition(transition)
}
ConstraintSet().apply {
clone(binding.root)
setTransformPivot(binding.view.id, binding.view.width / 2f, binding.view.height / 2f)
applyTo(binding.root)
}
binding.root.apply {
setTransition(R.id.transition_rotation_id)
setTransitionDuration(5000)
transitionToEnd()
}
}
}
}
We have defined setTransformPivot in a separate ConstraintSet since the ConstraintSet defined in the functions will change their values after the time specified in setTransitionDuration().
setTransformPivot — determine the coordinates around which the rotation will occur.
addTransition — adds a transition to the MotionScene(there may be several).
setTransition — sets the default transition to the MotionScene (only one).
getConstraintSet() — provides the ConstraintSet on the specified ID, if available in MotionScene.
The resulting ConstraintSet needs to be configured (how to change the value of a variable). Imagine a typical ConstreintSet setup in MotionScene xml.
LAUNCH 🥳🤩👏🕺💃
PS. vel_dan: Love what You DO 💚