android / views-widgets-samples

Multiple samples showing the best practices in views-widgets on Android.
Apache License 2.0
5.04k stars 3.01k forks source link

Multi-step Transition on MotionLayout #159

Open alfianyusufabdullah opened 3 years ago

alfianyusufabdullah commented 3 years ago

Hello, this is a question that starts with a curious. How to run some transition sequentially on MotionLayout? I read some references from Medium to use extensions awaitTransitionComplete but didn't work.

This is my motion scene XML

<?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:constraintSetStart="@id/base"
        motion:constraintSetEnd="@id/step2"
        motion:duration="3000">
    </Transition>

    <Transition
        motion:constraintSetStart="@id/step2"
        motion:constraintSetEnd="@id/step3"
        motion:autoTransition="animateToEnd"
        motion:duration="3000">
    </Transition>

    <Transition
        motion:constraintSetStart="@id/step3"
        motion:constraintSetEnd="@id/step4"
        motion:duration="3000">
    </Transition>

    <ConstraintSet android:id="@+id/base">
        <Constraint
            android:id="@+id/imageView"
            android:layout_width="10dp"
            android:layout_height="10dp"
            android:alpha="0.5"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
        <Constraint
            android:id="@+id/imageView2"
            android:layout_width="75dp"
            android:layout_height="75dp"
            android:alpha="0.5"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>

    <ConstraintSet
        android:id="@+id/step2"
        motion:deriveConstraintsFrom="@id/base">
        <Constraint
            android:id="@+id/imageView"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:alpha="0.5"
            android:rotation="30"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
        <Constraint
            android:id="@+id/imageView2"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:alpha="0.5"
            android:rotation="30"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>

    <ConstraintSet
        android:id="@+id/step3"
        motion:deriveConstraintsFrom="@id/step2">
        <Constraint
            android:id="@+id/imageView"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:alpha="1"
            android:rotation="-30"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
        <Constraint
            android:id="@+id/imageView2"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:alpha="1"
            android:rotation="-30"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>

    <ConstraintSet
        android:id="@+id/step4"
        motion:deriveConstraintsFrom="@id/step3">
        <Constraint
            android:id="@+id/imageView"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:alpha="1"
            android:rotation="0"
            android:scaleX="1000"
            android:scaleY="1000"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
        <Constraint
            android:id="@+id/imageView2"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:alpha="1"
            android:rotation="0"
            android:scaleX="1000"
            android:scaleY="1000"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>
</MotionScene>

Thank You!

Dosssik commented 3 years ago

Did you solve this? @alfianyusufabdullah Seems like I have a similar issue

leinardi commented 3 years ago

I managed to get it to work using this version of the awaitTransitionComplete extension:

/**
 * Wait for the transition to complete so that the given [transitionId] is fully displayed.
 *
 * @param transitionId The transition set to await the completion of
 * @param timeout Timeout for the transition to take place. Defaults to 5 seconds.
 */
@ExperimentalCoroutinesApi
suspend fun MotionLayout.awaitTransitionComplete(transitionId: Int, timeout: Long = 5000L) {
    var listener: MotionLayout.TransitionListener? = null
    try {
        withTimeout(timeout) {
            suspendCancellableCoroutine<Unit> { continuation ->
                listener = object : SimpleTransitionListener() {
                    override fun onTransitionCompleted(
                        motionLayout: MotionLayout,
                        currentId: Int
                    ) {
                        apply(::removeTransitionListener)
                        continuation.resume(Unit, Throwable::printStackTrace)
                    }
                }
                continuation.invokeOnCancellation {
                    listener?.apply(this@awaitTransitionComplete::removeTransitionListener)
                }
                addTransitionListener(listener)
            }
        }
    } catch (ex: TimeoutCancellationException) {
        listener?.apply(this::removeTransitionListener)
    }
}

open class SimpleTransitionListener : MotionLayout.TransitionListener {
    override fun onTransitionStarted(motionLayout: MotionLayout, startId: Int, endId: Int) {}

    override fun onTransitionChange(
        motionLayout: MotionLayout,
        startId: Int,
        endId: Int,
        progress: Float
    ) {
        // No-op
    }

    override fun onTransitionCompleted(motionLayout: MotionLayout, currentId: Int) {
        // No-op
    }

    override fun onTransitionTrigger(
        motionLayout: MotionLayout,
        triggerId: Int,
        endId: Boolean,
        progress: Float
    ) {
        // No-op
    }
}
        viewLifecycleOwner.lifecycleScope.launch {
                binding.motionLayout.setTransition(R.id.transition1)
                binding.motionLayout.transitionToEnd()
                binding.motionLayout.awaitTransitionComplete(R.id.set2)
                binding.motionLayout.setTransition(R.id.transition2)
                binding.motionLayout.transitionToEnd()
                binding.motionLayout.awaitTransitionComplete(R.id.set3)
                binding.motionLayout.setTransition(R.id.transition3)
                binding.motionLayout.transitionToEnd()
                binding.motionLayout.awaitTransitionComplete(R.id.set4)
                binding.motionLayout.setTransition(R.id.transition4)
                binding.motionLayout.transitionToEnd()
                binding.motionLayout.awaitTransitionComplete(R.id.set5)
            }

You just have to assign an ID to each Transition:

    <Transition
        android:id="@+id/transition1"
        app:constraintSetEnd="@+id/set2"
        app:constraintSetStart="@+id/set1"
        app:duration="500">