如何实现状态栏和工具栏的颜色变化动画(就像新日历应用程序一样)

38
新的Google日历应用程序拥有一种动画效果,我希望在我的应用程序中也能实现。当你创建一个新事件时,你可以选择该事件的颜色。当你这样做时,状态栏和工具栏会以一个圆形效果同时变为该颜色。
以下是我想实现的示例: Google Calendar new event toolbar and statusbar color change 我可以改变状态栏和工具栏的颜色,但是如何将圆形动画效果(或类似效果)应用于它们两个呢?

请查看此链接:http://stackoverflow.com/questions/27010265/implementing-ripple-effect-in-toolbar。 - Stephen
如果我正确理解了您链接的帖子,我认为它只是使工具栏在用户点击时具有涟漪效果(就像您会将涟漪添加到任何其他元素一样)。当选择微调器但不显示用户点击工具栏本身时,我想要同时动画化工具栏和状态栏。 - Shaun
我已更新问题,表明我不再认为效果与棒棒糖涟漪效果有关。看起来颜色变化是通过圆形效果进行动画处理的。 - Shaun
4个回答

40

我不知道他们如何实现波纹效果,但使用以下代码,您可以同时平稳过渡两个条的颜色

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);
}

谢谢您提供这个示例。如果我需要回退到其他方案,这正是我想要做的。我现在认为日历应用程序使用的动画效果与棒棒糖涟漪无关,而只是在状态栏和应用栏颜色变化时使用了圆形形状。我仍然想弄清楚他们如何在颜色变化时得到那个形状! - Shaun
1
我最终找到了如何做到这一点,但这种ValueAnimator方法是我在早期版本的设备上使用的。 - Shaun
很高兴知道你找到了解决方案。对于你的坚持表示赞赏!(: - fehbari
1
(int)new ArgbEvaluator().evaluate(from, to, ratio); 可以替代对 blendColors 的调用,并且它可以正确处理伽马值,而这种线性插值则不能。 - plinehan

40

我不知道日历应用程序是否确切采用了这种方式,但对我来说足够接近。

注意事项

  • 该方法使用引入于Lollipop的ViewAnimationUtils.createCircularReveal方法。
  • 它需要知道状态栏和工具栏操作栏的高度。您仍然可以使用?attr/actionBarSize来获取动态的操作栏高度和两个高度,但为简单起见,在此处假定操作栏高度为56dp,状态栏高度为24dp

通用思路

一般思路是将操作栏和状态栏设置为透明。这将使操作栏上移至状态栏下方,因此您必须调整操作栏的大小和填充来进行补偿。然后使用视图背后和ViewAnimationUtils.createCircularReveal来显示新的背景颜色。您还需要一个更多的视图在其后面以显示旧背景颜色,因为中间视图正在揭示新视图。

动画

动画需要:

  • 覆盖普通操作栏和状态栏空间的透明工具栏操作栏。在这种情况下,硬编码高度为56dp(操作栏)+ 24dp(状态栏)= 80dp。您还需要设置顶部填充为24dp,以使操作栏内容不会在状态栏下方。
  • 一个中间视图(我将其称为揭示视图),它与操作栏的大小相同(高度为80dp),但在操作栏后面。这将是ViewAnimationUtils.createCircularReveal作用的视图。
  • 一个底部视图(我将其称为揭示背景视图),其大小与揭示视图相同但位于其后面。这个视图用于在揭示视图在其顶部显示新颜色时显示旧背景颜色。

代码

以下是我使用的关键代码片段。请参见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>

颜色.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);
    }
}

注意事项

  • 注意工具栏、展示和背景视图上的android:elevation属性。如果工具栏上的高度较低,其他视图将会遮挡按钮和文字。

为什么我需要将宽度和高度除以2? - RoCkDevstack

4

经过大量研究,我找到了你想要的答案。

这种动画称为揭示动画,是在21.0 Android API - lollipop中引入的。不幸的是,它不兼容旧版本。

我找到了一个库,可以实现相同的揭示动画,但并非完全的向后兼容。但使用此库,您可以从API 14开始实现所需的效果。

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

谢谢!

如果您只想在lollipop上使用此动画,请搜索“Android Reveal Colour Animation implementation”。


谢谢你的指引。一开始我只是认为它只是用来显示隐藏的东西,这是正确的,但在你的评论后,我再次研究了一下,并成功地找到了如何将其用于此的方法。请查看我的采纳答案。 - Shaun

3
尝试这个方法,对我来说很有效,其效果与Google日历应用程序相同。
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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接