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