Search code examples
androidviewmodelandroid-constraintlayoutandroid-livedataandroid-motionlayout

MotionLayout seems to lose some constraints during animation


I have a problem: I have a TextView whose content is constantly changing via LiveData. This looks all right when the animation isn't executing, but when the MotionLayout starts executing, my text gets blocked a little bit. And the constraints of my TextView and Button are packed.

activity:

class ErrorActivity : AppCompatActivity() {

    private val mViewModel by viewModels<ErrorViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        DataBindingUtil.setContentView<ActivityErrorBinding>(this, R.layout.activity_error).apply {
            lifecycleOwner = this@ErrorActivity
            viewModel = mViewModel
        }
    }

    fun startStopAnim(v: View) {
        mViewModel.anim()
    }
}

activity layout:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:binding="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewModel"
            type="com.test.test.ErrorViewModel" />

    </data>

    <androidx.constraintlayout.motion.widget.MotionLayout
        android:id="@+id/layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutDescription="@xml/scene"
        binding:anim="@{viewModel.mAnim}">

        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{@string/test(viewModel.mCountDown)}"
            android:textSize="32sp"
            app:layout_constraintEnd_toStartOf="@+id/btn"
            app:layout_constraintHorizontal_chainStyle="packed"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="@string/test" />

        <Button
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Test"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/tv"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/anim"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="startStopAnim"
            android:text="startOrStop"
            android:textAllCaps="false"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.motion.widget.MotionLayout>
</layout>

viewModel:

class ErrorViewModel : ViewModel() {

    private val _countDown: MutableLiveData<String> = MutableLiveData()
    val mCountDown: LiveData<String> = _countDown

    private val _anim: MutableLiveData<Boolean> = MutableLiveData()
    val mAnim: LiveData<Boolean> = _anim

    private var count = 0
    private var state = false

    init {
        viewModelScope.launch(Dispatchers.IO) {
            while (true) {
                delay(1000)
                _countDown.postValue(count++.toString())
            }
        }
    }

    fun anim() {
        state = !state
        _anim.value = state
    }
}

@BindingAdapter("anim")
fun anim(layout: MotionLayout, play: Boolean?) {
    play?.let {
        if (it) {
            layout.transitionToEnd()
        } else {
            layout.transitionToStart()
        }
    }
}

motionScene:

For simplicity, Scene has only one duration

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto">
    <Transition
        app:constraintSetEnd="@+id/end"
        app:constraintSetStart="@+id/start"
        app:duration="2000" />
</MotionScene>

gif As you can see in the gif, when there is no click, everything works fine for the numbers, but when I click the button, a 2 second animation starts, during which time my text doesn't display properly. Of course, this is just a demo example. In a real scene, not only the text is not displayed completely, but even the TextView And ImageView are misplaced, and once the animation is executed, it cannot be recovered. Can someone help me...

Actually in a real scene, the parent layout (B) of the problematic TextView (A) would perform a displacement animation. As long as this animation is executed once, the constraint relationship of TextView A will definitely have problems and cannot be restored (onResume can be restored after onPause is currently found)


Solution

  • By design during animation MotionLayout does not honor requestLayout. Because in the majority of applications it is not needed and would have a significant impact on performance.

    To enable it in the transition add

    <Transition  ...   motion:layoutDuringTransition="honorRequest" \>