0

I have troubles with MotionLayout and MotionScene.

In the Motion Editor, I can see my animation and it looks fine (except for some artifacts), but when I run the app on the emulator, the animation doesn't match the Motion Editor view.

Motion Editor on the left, emulator on the right: enter image description here

I've been trying to fix this all day... Initially, I had a more complex animation. And initially I encountered the fact that in the editor the animation was displayed absolutely correctly, and in the emulator it always just grew out of the upper left corner of the screen.

I tried to find the reason for this and created simpler animations, but every time I ran into problems.

Moreover, I have an animation on the splash screen, but it works great and this animation is copied from there, only slightly modified. Rolling back the changes to the original view did not help either.

I also notice that the animation may not start at all, and this despite the fact that in some cases there were no changes.

I don't understand what to do at all.

What i tried to do:

  • completely clear the project from the cache, etc..
  • completely rewrite the MotionLayout again.
  • run animations later or on separate threads.
  • something else...

In the end, this Fragment looks like this:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="512dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/call_container"
            android:layout_width="match_parent"
            android:layout_height="304dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent">

            <androidx.constraintlayout.motion.widget.MotionLayout
                android:id="@+id/call_motion"
                android:paddingVertical="48dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layoutDescription="@xml/fragment_main_call_scene"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintBottom_toBottomOf="parent">

                <androidx.constraintlayout.widget.ConstraintLayout
                    android:id="@+id/call_motion_inner"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent">

                    <androidx.constraintlayout.widget.ConstraintLayout
                        android:layout_width="176dp"
                        android:layout_height="176dp"
                        android:background="@drawable/background_button"
                        app:layout_constraintTop_toTopOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintBottom_toBottomOf="parent">

                        <androidx.constraintlayout.widget.ConstraintLayout
                            android:id="@+id/call_title_container"
                            android:layout_width="match_parent"
                            android:layout_height="0dp"
                            app:layout_constraintTop_toTopOf="parent"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintEnd_toEndOf="parent"
                            app:layout_constraintBottom_toTopOf="@id/call_buttons_container">

                            <com.google.android.material.textview.MaterialTextView
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:layout_marginTop="32dp"
                                android:text="Call"
                                android:fontFamily="@font/bold"
                                android:lines="1"
                                android:textSize="32sp"
                                android:textAlignment="center"
                                android:ellipsize="end"
                                app:layout_constraintTop_toTopOf="parent"
                                app:layout_constraintBottom_toBottomOf="parent"
                                app:layout_constraintStart_toStartOf="parent"
                                app:layout_constraintEnd_toEndOf="parent" />

                        </androidx.constraintlayout.widget.ConstraintLayout>

                        <androidx.constraintlayout.widget.ConstraintLayout
                            android:id="@+id/call_buttons_container"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            app:layout_constraintTop_toBottomOf="@id/call_title_container"
                            app:layout_constraintBottom_toBottomOf="parent"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintEnd_toEndOf="parent">

                            <com.google.android.material.button.MaterialButton
                                android:id="@+id/call_fast_button"
                                android:layout_width="0dp"
                                android:layout_height="32dp"
                                android:layout_margin="8dp"
                                android:insetBottom="0dp"
                                android:insetTop="0dp"
                                android:minWidth="0dp"
                                android:text="Fast"
                                android:fontFamily="@font/regular"
                                app:cornerRadius="8dp"
                                app:layout_constraintTop_toTopOf="parent"
                                app:layout_constraintStart_toStartOf="parent"
                                app:layout_constraintBottom_toBottomOf="parent" />

                            <com.google.android.material.button.MaterialButton
                                android:id="@+id/call_slow_button"
                                android:layout_width="0dp"
                                android:layout_height="32dp"
                                android:layout_margin="8dp"
                                android:insetBottom="0dp"
                                android:insetTop="0dp"
                                android:minWidth="0dp"
                                android:text="Slow"
                                android:fontFamily="@font/regular"
                                app:cornerRadius="8dp"
                                app:layout_constraintTop_toTopOf="parent"
                                app:layout_constraintStart_toEndOf="@id/call_fast_button"
                                app:layout_constraintBottom_toBottomOf="parent"
                                app:layout_constraintEnd_toEndOf="parent" />

                        </androidx.constraintlayout.widget.ConstraintLayout>

                    </androidx.constraintlayout.widget.ConstraintLayout>

                </androidx.constraintlayout.widget.ConstraintLayout>

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

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

fragment_main_call_scene.xml:

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:motion="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <Transition
        motion:constraintSetEnd="@id/appearance_end"
        motion:constraintSetStart="@id/appearance_start"
        motion:motionInterpolator="@anim/cubic_bezier_interpolator">
        <KeyFrameSet>
        </KeyFrameSet>
    </Transition>

    <Transition
        motion:constraintSetEnd="@id/disappearance_end"
        motion:constraintSetStart="@id/disappearance_start"
        motion:motionInterpolator="@anim/cubic_bezier_interpolator">
        <KeyFrameSet>
        </KeyFrameSet>
    </Transition>

    <ConstraintSet android:id="@+id/appearance_start">
        <Constraint
            android:id="@id/call_motion_inner"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="48dp"
            android:translationX="256dp"
            android:alpha="0"
            motion:layout_constraintTop_toTopOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintBottom_toBottomOf="parent" />
    </ConstraintSet>

    <ConstraintSet android:id="@+id/appearance_end">
        <Constraint
            android:id="@id/call_motion_inner"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="48dp"
            android:translationX="0dp"
            android:alpha="1"
            motion:layout_constraintTop_toTopOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintBottom_toBottomOf="parent" />
    </ConstraintSet>

    <ConstraintSet android:id="@+id/disappearance_start">
        <Constraint
            android:id="@id/call_motion_inner"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="48dp"
            android:translationX="0dp"
            android:alpha="1"
            motion:layout_constraintTop_toTopOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintBottom_toBottomOf="parent" />
    </ConstraintSet>

    <ConstraintSet android:id="@+id/disappearance_end">
        <Constraint
            android:id="@id/call_motion_inner"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="48dp"
            android:translationX="-256dp"
            android:alpha="0"
            motion:layout_constraintTop_toTopOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintBottom_toBottomOf="parent" />
    </ConstraintSet>

    <ConstraintSet android:id="@+id/hide">
        <Constraint
            android:id="@id/call_motion_inner"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="48dp"
            android:translationX="2048dp"
            android:visibility="invisible"
            android:alpha="0"
            motion:layout_constraintTop_toTopOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintBottom_toBottomOf="parent" />
    </ConstraintSet>

</MotionScene>

Java code of fragment:

package com.some.app.components.main;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.some.app.R;
import com.some.app.core.BaseFragment;
import com.some.app.databinding.FragmentMainBinding;

import java.util.Timer;
import java.util.TimerTask;

public class MainFragment extends BaseFragment<FragmentMainBinding> {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        binding = FragmentMainBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                requireActivity().runOnUiThread(() -> {
                    getAnimator().startAppearanceAnimation(null);
                });
            }
        }, 3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                requireActivity().runOnUiThread(() -> getAnimator().startDisappearanceAnimation(null));
            }
        }, 6000);
    }

    @Override
    public Animator generateAnimator() {
        return new Animator() {
            @Override
            public void startAppearanceAnimation(Runnable onAppearanceAnimationFinished) {
                binding.callMotion.transitionToState(R.id.appearance_end, 666);
                if (onAppearanceAnimationFinished != null) {
                    Timer timer = new Timer();
                    timer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            onAppearanceAnimationFinished.run();
                        }
                    }, 666);
                }
            }

            @Override
            public void startDisappearanceAnimation(Runnable onDisappearanceAnimationFinished) {
                binding.callMotion.transitionToState(R.id.disappearance_end, 333);
                if (onDisappearanceAnimationFinished != null) {
                    Timer timer = new Timer();
                    timer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            binding.callMotion.jumpToState(R.id.hide);
                            timer.schedule(new TimerTask() {
                                @Override
                                public void run() {
                                    onDisappearanceAnimationFinished.run();
                                }
                            }, 333);
                        }
                    }, 333);
                }
            }
        };
    }

}

Any help is appreciated, thanks in advance!

wowandy
  • 1,124
  • 2
  • 10
  • 23

0 Answers0