如何在不自动播放过渡的情况下恢复MotionLayout的过渡状态? [英] How to restore transition state of MotionLayout without auto-playing the transition?

查看:81
本文介绍了如何在不自动播放过渡的情况下恢复MotionLayout的过渡状态?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的代码

活动

  class SwipeHandlerActivity:AppCompatActivity(R.layout.activity_swipe_handler){重写fun onSaveInstanceState(outState:Bundle){super.onSaveInstanceState(outState)outState.putBundle("Foo",findViewById< MotionLayout>(R.id.the_motion_layout).transitionState)}重写fun onCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)savedInstanceState?.getBundle("Foo")?. let(findViewById< MotionLayout>(R.id.the_motion_layout):: setTransitionState)}} 

布局

 <?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-autoandroid: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"><查看android:id ="@@ id/touchAnchorView"android:background ="#8309ACandroid: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> 

场景

 <?xml version ="1.0"encoding ="utf-8"?< MotionScenexmlns:android ="http://schemas.android.com/apk/res/android"xmlns:motion ="http://schemas.android.com/apk/res-auto"><过渡运动:constraintSetEnd ="@ + id/end";运动:constraintSetStart ="@ id/开始"运动:持续时间="1000"<在刷卡motion:touchAnchorId =" @ id/imageView"motion:dragDirection ="dragUp"motion:touchAnchorSide ="top"/></过渡>< ConstraintSet android:id =" @@ id/start"></ConstraintSet>< ConstraintSet android:id =" @ + id/end"><约束android:layout_height ="250dp";运动:layout_constraintStart_toStartOf ="@@ id/textView2";运动:layout_constraintEnd_toEndOf ="@@ id/textView2";android:layout_width ="250dp";android:id ="@@ id/imageView"运动:layout_constraintBottom_toTopOf ="@@ id/textView2";android:layout_marginBottom =" 68dp"/></ConstraintSet></MotionScene> 

观察到的行为

预期行为

更改布局后,运动布局将保持其初始状态

编辑(hacky解决方案)

我最终创建了这些扩展功能

  funmotionLayout.restoreState(savedInstanceState:Bundle ?, key:String){viewTreeObserver.addOnGlobalLayoutListener(object:ViewTreeObserver.OnGlobalLayoutListener {覆盖fun onGlobalLayout(){doRestore(savedInstanceState,键)viewTreeObserver.removeOnGlobalLayoutListener(this)}})}私人乐趣MotionLayout.doRestore(savedInstanceState:Bundle ?, key:String)=savedInstanceState?.let {val motionBundle = savedInstanceState.getBundle(key)?:错误(未保存$ key状态")setTransition(motionBundle.getInt("claptrap.motion.startState",-1).takeIf {it!= -1}?:错误(无法检索$ key的开始状态"),motionBundle.getInt("claptrap.motion.endState",-1).takeIf {it!= -1}?:错误(无法检索$ key的结束状态"))进度= motionBundle.getFloat("claptrap.motion.progress",-1.0f).takeIf {它!= -1.0f}?:错误(无法检索$ key的进度")}fun MotionLayout.saveState(outState:Bundle,key:String){outState.putBundle(钥匙,bundleOf("claptrap.motion.startState";进入startState,"claptrap.motion.endState";到endState,"claptrap.motion.progress"进步))} 

然后我这样称呼他们:

onCreate onCreateView

  if(savedInstanceState!= null){binding.transactionsMotionLayout.restoreState(savedInstanceState,MOTION_LAYOUT_STATE_KEY)} 

onSaveInstanceState

 <代码> binding.transactionsMotionLayout.saveState(outState,MOTION_LAYOUT_STATE_KEY) 

这导致了 Activity s中的 MotionLayouts Fragment s中的 MotionLayouts 的预期行为.但是我对所需的代码量不满意,因此,如果有人可以提出更干净的解决方案,我将非常高兴听到:)

解决方案

我不明白为什么没有这样做,但需要扩展 MotionLayout 才能正确保存视图状态./p>

您当前的解决方案(除了需要在活动和片段层中进行额外处理)在可分离片段的情况下将失败,因为它们在内部处理视图保存状态而没有实际填充 savedInstanceState (我知道这很令人困惑).

 打开类SavingMotionLayout @JvmOverloads构造函数(context:上下文,attrs:AttributeSet?= null,defStyleAttr:Int = 0):MotionLayout(context,attrs,defStyleAttr){重写fun onSaveInstanceState():可打包{返回SaveState(super.onSaveInstanceState(),startState,endState,targetPosition)}重写fun onRestoreInstanceState(state:Parcelable?){(状态为?SaveState)?. let {super.onRestoreInstanceState(it.superParcel)setTransition(it.startState,it.endState)进度= it.progress}}@ kotlinx.android.parcel.Parcelize私有类SaveState(val superParcel:可包裹吗?,val startState:Int,val endState:Int,val进展:浮动):可包裹} 

您将需要在XML中使用此类,而不是 MotionLayout ,但是此类不太容易出错,并且会遵循适当的视图状态保存机制,因此您无需向活动或碎片了.

如果要禁用保存,可以使用XML中的 android:saveEnabled ="false" 或代码中的 isSaveEnabled = false 进行保存.

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 :)

解决方案

I don't see why you haven't done that but you need to extend MotionLayout to properly save the view state.

Your current solution (aside from requiring extra handling in activity and fragment layer) will fail in case of detachable fragments because they handle views save state internally without actually populating savedInstanceState (quite confusing I know).

open class SavingMotionLayout @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : MotionLayout(context, attrs, defStyleAttr) {

    override fun onSaveInstanceState(): Parcelable {
        return SaveState(super.onSaveInstanceState(), startState, endState, targetPosition)
    }

    override fun onRestoreInstanceState(state: Parcelable?) {
        (state as? SaveState)?.let {
            super.onRestoreInstanceState(it.superParcel)
            setTransition(it.startState, it.endState)
            progress = it.progress
        }
    }

    @kotlinx.android.parcel.Parcelize
    private class SaveState(
            val superParcel: Parcelable?,
            val startState: Int,
            val endState: Int,
            val progress: Float
    ) : Parcelable
}

You will need to use this class in your XML instead of MotionLayout however its less prone to errors and will respect proper view state saving mechanisms so you do not need to add any extra code to activities or fragments anymore.

If you want to disable saving you can do it with android:saveEnabled="false" in XML or isSaveEnabled = false in code.

这篇关于如何在不自动播放过渡的情况下恢复MotionLayout的过渡状态?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆