How to restore transition state of MotionLayout without auto-playing the transition?

跟風遠走 提交于 2021-01-05 07:21:06

问题


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 Activitys, and MotionLayouts inside Fragments. 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 :)

来源:https://stackoverflow.com/questions/65048418/how-to-restore-transition-state-of-motionlayout-without-auto-playing-the-transit

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!