My code
Activity
class SwipeHandlerActivity : AppCompatActivity(R.layout.activity_swipe_handler){
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBundle("Foo", findViewById<MotionLayout>(R.id.the_motion_layout).transitionState)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedInstanceState?.getBundle("Foo")?.let(findViewById<MotionLayout>(R.id.the_motion_layout)::setTransitionState)
}
}
Layout
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/activity_swipe_handler_scene"
android:id="@+id/the_motion_layout"
app:motionDebug="SHOW_ALL">
<View
android:id="@+id/touchAnchorView"
android:background="#8309AC"
android:layout_width="64dp"
android:layout_height="64dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.motion.widget.MotionLayout>
Scene
<?xml version="1.0" encoding="utf-8"?>
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<OnSwipe
motion:touchAnchorId="@id/imageView"
motion:dragDirection="dragUp"
motion:touchAnchorSide="top" />
</Transition>
<ConstraintSet android:id="@+id/start">
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:layout_height="250dp"
motion:layout_constraintStart_toStartOf="@+id/textView2"
motion:layout_constraintEnd_toEndOf="@+id/textView2"
android:layout_width="250dp"
android:id="@+id/imageView"
motion:layout_constraintBottom_toTopOf="@+id/textView2"
android:layout_marginBottom="68dp" />
</ConstraintSet>
</MotionScene>
Observed behavior
Expected behavior
The motion layout stays at its start state after configuration change
Edit (hacky solution)
I ended up creating these extension functions
fun MotionLayout.restoreState(savedInstanceState: Bundle?, key: String) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
doRestore(savedInstanceState, key)
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
}
private fun MotionLayout.doRestore(savedInstanceState: Bundle?, key: String) =
savedInstanceState?.let {
val motionBundle = savedInstanceState.getBundle(key) ?: error("$key state was not saved")
setTransition(
motionBundle.getInt("claptrap.motion.startState", -1)
.takeIf { it != -1 }
?: error("Could not retrieve start state for $key"),
motionBundle.getInt("claptrap.motion.endState", -1)
.takeIf { it != -1 }
?: error("Could not retrieve end state for $key")
)
progress = motionBundle.getFloat("claptrap.motion.progress", -1.0f)
.takeIf { it != -1.0f }
?: error("Could not retrieve progress for $key")
}
fun MotionLayout.saveState(outState: Bundle, key: String) {
outState.putBundle(
key,
bundleOf(
"claptrap.motion.startState" to startState,
"claptrap.motion.endState" to endState,
"claptrap.motion.progress" to progress
)
)
}
Then I called them like this:
onCreate
, onCreateView
if (savedInstanceState != null) {
binding.transactionsMotionLayout.restoreState(savedInstanceState, MOTION_LAYOUT_STATE_KEY)
}
onSaveInstanceState
binding.transactionsMotionLayout.saveState(outState, MOTION_LAYOUT_STATE_KEY)
This resulted in the expected behavior for both MotionLayouts
in Activity
s, and MotionLayouts
inside Fragment
s. But I'm not happy with the amount of code required, so if anyone could suggest a cleaner solution, I would be really happy to hear that :)