#android #android-animation #android-motionlayout
#Android #android-анимация #android-motionlayout
Вопрос:
Мой код
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>
Наблюдаемое поведение
Ожидаемое поведение
Макет движения остается в начальном состоянии после изменения конфигурации
Редактировать (хакерское решение)
В итоге я создал эти функции расширения
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
)
)
}
Затем я назвал их так:
onCreate
, onCreateView
if (savedInstanceState != null) {
binding.transactionsMotionLayout.restoreState(savedInstanceState, MOTION_LAYOUT_STATE_KEY)
}
onSaveInstanceState
binding.transactionsMotionLayout.saveState(outState, MOTION_LAYOUT_STATE_KEY)
Это привело к ожидаемому поведению как MotionLayouts
в Activity
s, так и MotionLayouts
внутри Fragment
s. Но я недоволен количеством требуемого кода, поэтому, если кто-нибудь может предложить более чистое решение, я был бы очень рад это услышать 🙂
Ответ №1:
Я не понимаю, почему вы этого не сделали, но вам нужно расширить MotionLayout
, чтобы правильно сохранить состояние просмотра.
Ваше текущее решение (помимо необходимости дополнительной обработки в activity и fragment layer) потерпит неудачу в случае съемных фрагментов, потому что они обрабатывают представления, сохраняют состояние внутри, фактически не заполняя savedInstanceState
(я знаю, это довольно запутанно).
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
}
Вам нужно будет использовать этот класс в вашем XML вместо MotionLayout
того, чтобы, однако, он менее подвержен ошибкам и будет соблюдать надлежащие механизмы сохранения состояния просмотра, поэтому вам больше не нужно добавлять дополнительный код к действиям или фрагментам.
Если вы хотите отключить сохранение, вы можете сделать это с android:saveEnabled="false"
помощью XML или isSaveEnabled = false
кода.
Комментарии:
1. Сработало как шарм, спасибо!
2. Не забудьте определить идентификатор представления для макета движения, если у макета движения нет идентификатора, сохранение и восстановление не будут работать.
Ответ №2:
У MotionLayout есть методы:
Bundle getTransitionState()
и
void setTransitionState(Bundle bundle)
Возвращенный и установленный пакет содержит
Прогресс, скорость, начальное состояние и конечное состояние.
Для простых MotionLayouts это можно использовать. Возможно иметь более сложные MotionLayouts, где этой информации недостаточно.
Комментарии:
1. Ну, как вы можете видеть в моем коде активности, я делаю именно то, что вы сказали, и, как вы можете видеть в gif, это не работает для простейших случаев.