How to animate floating action button using android activity transition?
Question is old, however it is still interesting. That's how I implemented this:
First of all you need to create two ViewGroups
which in transition api are called "scenes". First scene contains views before transition, second one contain views after transition. Then you should just replace first scene with second one and provide Transition
which describes how views from first scene goes to second scene, also how views in first scene disappear and how views in second scene appear.
In this example there is difficulty with fab button, because it is the only view which is animated out of white area of "scene root" screen. Thats why scenes are fullscreen with margin top which equals to height of blue "monday" header.
All transitions here are default except of yellow background transition. Yellow background view appears in second scene with circular reveal animation. And it dissapear with circular reveal collapse animation. There is no default animation like this, thats why I wrote custom one:
import android.animation.Animator;import android.animation.AnimatorListenerAdapter;import android.support.transition.TransitionValues;import android.support.transition.Visibility;import android.view.View;import android.view.ViewAnimationUtils;import android.view.ViewGroup;public class CircularRevealTransition extends Visibility {@Overridepublic Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { int startRadius = 0; int endRadius = (int) Math.hypot(view.getWidth(), view.getHeight()); Animator reveal = ViewAnimationUtils.createCircularReveal(view, view.getWidth() / 2, view.getHeight() / 2, startRadius, endRadius); //make view invisible until animation actually starts view.setAlpha(0); reveal.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { view.setAlpha(1); } }); return reveal;}@Overridepublic Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { int endRadius = 0; int startRadius = (int) Math.hypot(view.getWidth(), view.getHeight()); Animator reveal = ViewAnimationUtils.createCircularReveal(view, view.getWidth() / 2, view.getHeight() / 2, startRadius, endRadius); return reveal;}}
Full code is here:
import android.os.Bundle;import android.support.annotation.NonNull;import android.support.design.widget.FloatingActionButton;import android.support.transition.ArcMotion;import android.support.transition.ChangeBounds;import android.support.transition.Fade;import android.support.transition.Scene;import android.support.transition.Slide;import android.support.transition.Transition;import android.support.transition.TransitionListenerAdapter;import android.support.transition.TransitionManager;import android.support.transition.TransitionSet;import android.support.v7.app.AppCompatActivity;import android.view.Gravity;import android.view.View;import android.view.ViewGroup;public class MainActivity extends AppCompatActivity { private ViewGroup mSceneRoot; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setSupportActionBar(findViewById(R.id.toolbar)); mSceneRoot = findViewById(R.id.sceneRoot); //show scene1 without animation showScene1(false); } private void showScene1(boolean animated) { ViewGroup root = (ViewGroup) getLayoutInflater().inflate(R.layout.scene1, null); FloatingActionButton fab = root.findViewById(R.id.fab); fab.setOnClickListener(v -> { showScene2(); }); Scene scene = new Scene(mSceneRoot, root); Transition transition = animated ? getScene1Transition() : null; TransitionManager.go(scene, transition); } private void showScene2() { ViewGroup root = (ViewGroup) getLayoutInflater().inflate(R.layout.scene2, null); View btnBack = root.findViewById(R.id.btnCancel); btnBack.setOnClickListener(v -> { showScene1(true); }); Scene scene = new Scene(mSceneRoot, root); Transition transition = getScene2Transition(); TransitionManager.go(scene, transition); } private Transition getScene2Transition() { TransitionSet set = new TransitionSet(); //fab changes position ChangeBounds changeTransform = new ChangeBounds(); changeTransform.addListener(new TransitionListenerAdapter() { @Override public void onTransitionEnd(@NonNull Transition transition) { //hide fab button on the end of animation mSceneRoot.findViewById(R.id.fab).setVisibility(View.INVISIBLE); } }); changeTransform.addTarget(R.id.fab); changeTransform.setDuration(300); //fab arc path ArcMotion arcMotion = new ArcMotion(); arcMotion.setMaximumAngle(45); arcMotion.setMinimumHorizontalAngle(90); arcMotion.setMinimumVerticalAngle(0); changeTransform.setPathMotion(arcMotion); set.addTransition(changeTransform); //bg circular reveal animation starts CircularRevealTransition crt = new CircularRevealTransition(); crt.addTarget(R.id.yellowBG); crt.setStartDelay(200); crt.setDuration(600); set.addTransition(crt); //buttons appear Fade fade = new Fade(); fade.addTarget(R.id.btnBegin); fade.addTarget(R.id.btnCancel); fade.addTarget(R.id.text); fade.setStartDelay(600); set.addTransition(fade); //left buttons column slide to left Slide slide = new Slide(Gravity.LEFT); slide.addTarget(R.id.slideLeftContainer); set.addTransition(slide); //right buttons column slide to right Slide slide2 = new Slide(Gravity.RIGHT); slide2.addTarget(R.id.slideRightContainer); set.addTransition(slide2); return set; } private Transition getScene1Transition() { TransitionSet set = new TransitionSet(); //buttons from scene2 fade out Fade fade = new Fade(); fade.addTarget(R.id.btnBegin); fade.addTarget(R.id.btnCancel); fade.addTarget(R.id.text); set.addTransition(fade); //Circular Reveal collapse animation starts CircularRevealTransition crt = new CircularRevealTransition(); crt.addTarget(R.id.yellowBG); crt.setDuration(600); set.addTransition(crt); //then fab button changes position ChangeBounds changeTransform = new ChangeBounds(); changeTransform.addTarget(R.id.fab); changeTransform.setDuration(300); changeTransform.setStartDelay(500); //arc path ArcMotion arcMotion = new ArcMotion(); arcMotion.setMaximumAngle(45); arcMotion.setMinimumHorizontalAngle(90); arcMotion.setMinimumVerticalAngle(0); changeTransform.setPathMotion(arcMotion); set.addTransition(changeTransform); //left buttons column slide in from left Slide slide = new Slide(Gravity.LEFT); slide.addTarget(R.id.slideLeftContainer); slide.setStartDelay(500); set.addTransition(slide); //right buttons column slide in from right Slide slide2 = new Slide(Gravity.RIGHT); slide2.addTarget(R.id.slideRightContainer); slide2.setStartDelay(500); set.addTransition(slide2); return set; }}
activity_main.xml
:
<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:titleTextColor="#fff" /> <FrameLayout android:id="@+id/topContainer" android:layout_width="match_parent" android:layout_height="@dimen/header_height" android:background="#00BCD4" android:orientation="vertical" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/toolbar"> <TextView android:layout_width="match_parent" android:layout_height="36dp" android:layout_gravity="bottom" android:background="#9000" android:gravity="center_vertical" android:paddingLeft="8dp" android:text="MONDAY" android:textColor="#fff" /> </FrameLayout> <FrameLayout android:id="@+id/sceneRoot" android:layout_width="match_parent" android:layout_height="0dp" android:clipChildren="false" android:clipToPadding="false" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/toolbar" /></android.support.constraint.ConstraintLayout>
scene1.xml
:
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/root"android:layout_width="match_parent"android:layout_height="match_parent"android:clipChildren="false"android:clipToPadding="false"><LinearLayout android:id="@+id/slideLeftContainer" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_marginTop="@dimen/header_height" android:orientation="vertical"> <include layout="@layout/button" /> <include layout="@layout/button" /> <include layout="@layout/button" /></LinearLayout><LinearLayout android:id="@+id/slideRightContainer" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="right" android:layout_marginTop="@dimen/header_height" android:orientation="vertical"> <include layout="@layout/button" /> <include layout="@layout/button" /> <include layout="@layout/button" /></LinearLayout><android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:layout_marginTop="@dimen/fab_margin_top" android:layout_marginRight="@dimen/fab_margin" android:clickable="true" android:focusable="true" app:backgroundTint="#FFEE4D" app:srcCompat="@drawable/ic_add_black_24dp" /></FrameLayout>
scene2.xml
:
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/root"android:layout_width="match_parent"android:layout_height="match_parent"android:clipChildren="false"android:clipToPadding="false"android:paddingTop="@dimen/header_height"><View android:id="@+id/yellowBG" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFEE4D" /><android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="@dimen/fab_margin" app:backgroundTint="#FFEE4D" app:srcCompat="@drawable/ic_add_black_24dp" /><Button android:id="@+id/btnBegin" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|left" android:layout_margin="@dimen/fab_margin" android:padding="16dp" android:text="BEGIN" android:textSize="22sp" /><TextView android:id="@+id/text" android:layout_width="@dimen/header_height" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt" android:textSize="22sp" /><Button android:id="@+id/btnCancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:layout_margin="@dimen/fab_margin" android:backgroundTint="#FF5151" android:padding="16dp" android:text="CANCEL" android:textSize="22sp" /></FrameLayout>
button.xml
:
<?xml version="1.0" encoding="utf-8"?><Button xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:padding="24dp" android:text="6:30" android:textSize="24sp" />
values.xml
:
<?xml version="1.0" encoding="utf-8"?><resources> <string name="app_name">TestTransition</string> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#303F9F</color> <color name="colorAccent">#FF4081</color> <dimen name="fab_margin">16dp</dimen> <dimen name="header_height">200dp</dimen> <dimen name="fab_margin_top">132dp</dimen></resources>
I think what you're looking for are Meaningful Transitions.
In pre-lollipop devices this can be achieved with ActivityOptionsCompat
helper.
Some useful links that might help you:
- a simple material design tutorial (with source code) which usesActivityOptionsCompat for transitions.
- Transitions example on the Android Developrs blog (scroll down abit).
- Defining Custom Animations in the Android Developers training
guide