How can I Animate the color change of the statusbar and toolbar (like the new Calendar app does)

22,070

Solution 1

I don't know if this is the exact way the Calendar app does it, but it's close enough for me.

Caveats

  • The method uses the ViewAnimationUtils.createCircularReveal method introduced in Lollipop.
  • It requires knowing the height of the status bar and your toolbar actionbar. You can still use ?attr/actionBarSize for your actionbar and get both dynamically, but for simplicity here I've assumed 56dp for the actionbar height and 24dp for the status bar height.

General Idea

The general idea is to set your actionbar and status bar to transparent. This will shift your actionbar up under the statusbar so you have to adjust the size and padding of the actionbar to compensate. You then use a view behind it and ViewAnimationUtils.createCircularReveal to reveal the new background color. You need one more view behind that to show the old background color as the middle view is revealing the new one.

The Animation

The animation requires:

  • The transparent toolbar actionbar that covers the space of the regular actionbar and the statusbar. The hard-coded height, in this case, is 56dp (actionbar) + 24dp (statusbar) = 80dp. You also need to set the top padding to 24dp to keep the actionbar content our from under the statusbar.
  • A middle view (I'll call it the reveal view) that's the same size (80dp height) but just behind the actionbar. This will be the view the ViewAnimationUtils.createCircularReveal acts on.
  • A bottom view (I'll call it the reveal background view) that's the same size as the reveal view but behind it. This view is there to show the old background color while the reveal view is revealing the new color on top of it.

Code

Here are the key pieces of code I used. See the example project at https://github.com/shaun-blake-experiments/example-toolbar-animation.

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <View
        android:id="@+id/revealBackground"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:paddingTop="24dp"
        android:background="@color/primary"
        android:elevation="4dp">
    </View>

    <View
        android:id="@+id/reveal"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:paddingTop="24dp"
        android:background="@color/primary"
        android:elevation="4dp">
    </View>

    <Toolbar
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:paddingTop="24dp"
        android:background="@android:color/transparent"
        android:elevation="4dp"
        android:theme="@style/TranslucentActionBar">
        </Toolbar>

    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Invert Toolbar Colors"
        android:textOn="Invert Toolbar Colors On"
        android:textOff="Invert Toolbar Colors Off"
        android:id="@+id/toggleButton"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

styles.xml

<resources>
    <style name="AppTheme" parent="@android:style/Theme.Material.Light.NoActionBar">
        <item name="android:windowTranslucentStatus">true</item>
        <item name="android:windowContentOverlay">@null</item>
    </style>

    <style name="TranslucentActionBar" parent="@android:style/Widget.Material.ActionBar">
        <item name="android:textColorPrimary">@color/primary_text_dark_background</item>
    </style>
</resources>

colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="primary">#2196F3</color>
    <color name="primary_dark">#1976D2</color>
    <color name="primary_light">#BBDEFB</color>
    <color name="accent">#009688</color>
    <color name="primary_text">#DD000000</color>
    <color name="primary_text_dark_background">#FFFFFF</color>
    <color name="secondary_text">#89000000</color>
    <color name="icons">#FFFFFF</color>
    <color name="divider">#30000000</color>
</resources>

MainActivity.java

package com.example.android.toolbaranimation;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.widget.ToggleButton;
import android.widget.Toolbar;


public class MainActivity extends Activity {

    private View mRevealView;
    private View mRevealBackgroundView;
    private Toolbar mToolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mToolbar = (Toolbar) findViewById(R.id.appbar);
        mToolbar.setTitle(getString(R.string.app_name));

        mRevealView = findViewById(R.id.reveal);
        mRevealBackgroundView = findViewById(R.id.revealBackground);

        ToggleButton toggleButton = (ToggleButton) findViewById(R.id.toggleButton);
        toggleButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                boolean on = ((ToggleButton) v).isChecked();

                if (on) {
                    animateAppAndStatusBar(R.color.primary, R.color.accent);
                } else {
                    animateAppAndStatusBar(R.color.accent, R.color.primary);
                }
            }
        });

        setActionBar(mToolbar);
    }

    private void animateAppAndStatusBar(int fromColor, final int toColor) {
        Animator animator = ViewAnimationUtils.createCircularReveal(
                mRevealView,
                mToolbar.getWidth() / 2,
                mToolbar.getHeight() / 2, 0,
                mToolbar.getWidth() / 2);

        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                mRevealView.setBackgroundColor(getResources().getColor(toColor));
            }
        });

        mRevealBackgroundView.setBackgroundColor(getResources().getColor(fromColor));
        animator.setStartDelay(200);
        animator.setDuration(125);
        animator.start();
        mRevealView.setVisibility(View.VISIBLE);
    }
}

Notes

  • Be careful of the android:elevation property on the toolbar, reveal, and reveal background views. If the elevation is lower on the toolbar, the others will cover the buttons and text.

Solution 2

I don't know how they achieved the ripple effect, but you can have a smooth color transition of both bars simultaneously with the following code.

private void tintSystemBars() {
    // Initial colors of each system bar.
    final int statusBarColor = getResources().getColor(R.color.status_bar_color);
    final int toolbarColor = getResources().getColor(R.color.toolbar_color);

    // Desired final colors of each bar.
    final int statusBarToColor = getResources().getColor(R.color.status_bar_to_color);
    final int toolbarToColor = getResources().getColor(R.color.toolbar_to_color);

    ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // Use animation position to blend colors.
            float position = animation.getAnimatedFraction();

            // Apply blended color to the status bar.
            int blended = blendColors(statusBarColor, statusBarToColor, position);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                getWindow.setStatusBarColor(blended);
            }

            // Apply blended color to the ActionBar.
            blended = blendColors(toolbarColor, toolbarToColor, position);
            ColorDrawable background = new ColorDrawable(blended);
            getSupportActionBar().setBackgroundDrawable(background);
        }
    });

    anim.setDuration(500).start();
}

private int blendColors(int from, int to, float ratio) {
    final float inverseRatio = 1f - ratio;

    final float r = Color.red(to) * ratio + Color.red(from) * inverseRatio;
    final float g = Color.green(to) * ratio + Color.green(from) * inverseRatio;
    final float b = Color.blue(to) * ratio + Color.blue(from) * inverseRatio;

    return Color.rgb((int) r, (int) g, (int) b);
}

Solution 3

After A great amount of research ,

I've found an answer that you'd want.

This animation is called a reveal animation introduced in the 21.0 Android API - lollipop . Unfortunately , it is not backwards compatible.

I've found a library which does the same reveal animation but not exactly a backport , but the effect you want can be achieved API 14 onwards with this library

https://github.com/markushi/android-ui

Thank You,

If you are keen on using this animation only with lollipop then just google "Android Reveal Colour Animation implementation".

Solution 4

Try this, it works great for me and it gets the same effect Google Calendar app does.

private void reveal(CollapsingToolbarLayout toolbarLayout, int colorPrimary, int colorPrimaryDark){
    // get the center for the clipping circle
    int cx = toolbarLayout.getWidth() / 2;
    int cy = toolbarLayout.getHeight() / 2;

    // get the final radius for the clipping circle
    float finalRadius = (float) Math.hypot(cx, cy);

    // create the animator for this view (the start radius is zero)
    Animator anim =
            ViewAnimationUtils.createCircularReveal(toolbarLayout, cx, cy, 0, finalRadius);

    // make the view visible and start the animation
    toolbarLayout.setBackgroundColor(colorPrimary);
    anim.start();
    Window window = getWindow();
    window.setStatusBarColor(colorPrimaryDark);
    toolbarLayout.setContentScrimColor(colorPrimary);
}
Share:
22,070
Shaun
Author by

Shaun

Updated on July 21, 2022

Comments

  • Shaun
    Shaun almost 2 years

    The new Google Calendar app has an animation I would like to do in my app. When you create a new event you can choose a color for the event. When you do, the statusbar and toolbar change to that color with a circular effect that covers both of them.

    Here's an example of what I'd like to do:

    Google Calendar new event toolbar and statusbar color change

    I can change the color of the statusbar and toolbar, but how can I apply the circular animation effect (or similar) to both of them as the color is changed?

  • Shaun
    Shaun over 9 years
    Thank you for this example. It's exactly what I wanted to do if I needed to fallback to something else. I now think the animation effect the calendar app is using isn't related to the lollipop ripple but just a circular shape on the color change of the status bar and app bar. I'd still like to figure out how they get that shape on their color changes!
  • Shaun
    Shaun over 9 years
    Thanks for the pointer. I initially thought it was just to show hidden things, which is true, but after your comment I looked into it again and was able to figure out how to use it for this. See my accepted answer.
  • Shaun
    Shaun over 9 years
    I ended up figuring out how to do it but this ValueAnimator approach is what I use on pre-lolllipop devices.
  • fehbari
    fehbari over 9 years
    Glad to know you found a solution. Kudos for the persistence! (:
  • RoCkDevstack
    RoCkDevstack about 6 years
    Why do I need to divide the width and height to 2?
  • plinehan
    plinehan over 5 years
    (int)new ArgbEvaluator().evaluate(from, to, ratio); can be used in place of the call to blendColors, and it will handle gamma correctly, which this linear interpolation won't.