Mimicking the ToolBar effects of Social Steps

animation







0

Edit recommendation: Rare earth Nuggets, this is an application for technical developers, you can get the latest and best quality technology nuggets, not only Android knowledge, front-end, back-end as well as products and design dabbler, want to become a friend of full stack engineers do not miss it!

Some time ago, I saw an interesting article on Medium called Toolbar Delight. This article explains how to achieve the following effect:

GIF doesn’t look good, see the video for a clearer version of the original article.

The article is good, but the code is incomplete, and there are some details the author didn’t reveal. So I looked it over and decided to create a similar effect myself, with a similarity of 95 or more.

In fact, this is still very simple, are some details, can be roughly decomposed into:

  1. Gradient from left to right. That’s easy.

  2. When rolling, the radian changes with the verticalOffset of AppBarLayout. When folding, the color gradually transitions to colorPrimary, and the cloud also runs to the boundary when folding.

  3. The color is different at different times, and the position of the sun or moon is as similar as possible to the real world. This isn’t hard. Just break it up into sections of the day.

  4. When the screen opens, there is an animation that transitions from the previous period state to the current state. My implementation here is slightly different from the original, but it’s easy to do exactly the same.

The sun, stars, and clouds are all bitmaps from Social Steps.

Well, let’s get straight to what I finally achieved:

Above is the effect of 19.44 at night, other time is not the same picture.

Most of the effects are implemented in a custom view called ToolbarArcBackground:

ToolbarArcBackground.java

public class ToolbarArcBackground extends View {
    private float scale = 1;
 
    private float timeRate;
 
    private int gradientColor1 = 0xff4CAF50;
    private int gradientColor2 = 0xFF0E3D10;
    private int lastGradientColor1 = 0xff4CAF50;
    private int lastGradientColor2 = 0xFF0E3D10;
    private Context context;
 
    private Bitmap sun;
    private Bitmap sunMorning;
    private Bitmap sunNoon;
    private Bitmap sunEvening;
 
    private Bitmap cloud1;
    private Bitmap cloud2;
    private Bitmap cloud3;
 
    private Bitmap moon;
    private Bitmap star;
 
    private int cloud1X = 50;
    private int cloud2X = 450;
    private int cloud3X = 850;
 
    int waveHeight = 60;
 
    private int cloud1Y = waveHeight + 150;
    private int cloud2Y = waveHeight + 120;
    private int cloud3Y = waveHeight + 20;
 
 
 
    private int sunHeight;
 
    private Day now = Day.MORNING;
 
    enum Day {
        MORNING, NOON, AFTERNOON, EVENING,
        MIDNIGHT
    }
 
    public ToolbarArcBackground(Context context) {
        super(context);
        this.context = context;
        init();
    }
 
    public ToolbarArcBackground(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }
 
    public ToolbarArcBackground(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        init();
    }
 
    public void setScale(float scale) {
        this.scale = scale;
        invalidate();
    }
 
    private void init() {
        calculateTimeLine();
        createBitmaps();
        initGradient();
    }
 
    private void initGradient(){
        switch (now) {
            case MORNING:
                lastGradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_midnight);
                lastGradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_midnight);
 
                gradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_morning);
                gradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_morning);
                break;
            case NOON:
                lastGradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_morning);
                lastGradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_morning);
 
                gradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_noon);
                gradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_noon);
                break;
            case AFTERNOON:
                lastGradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_noon);
                lastGradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_noon);
 
                gradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_noon_evening);
                gradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_noon_evening);
                break;
            case EVENING:
                lastGradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_noon_evening);
                lastGradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_noon_evening);
 
                gradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_evening);
                gradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_evening);
                break;
            case MIDNIGHT:
                lastGradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_evening);
                lastGradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_evening);
 
                gradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_midnight);
                gradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_midnight);
                break;
        }
    }
 
    private void calculateTimeLine() {
        Date d = new Date();
        if (d.getHours() > 5 && d.getHours() < 11) {
            now = Day.MORNING;
        } else if (d.getHours() < 13 && d.getHours() >= 11) {
            now = Day.NOON;
        } else if (d.getHours() < 18 && d.getHours() >= 13) {
            now = Day.AFTERNOON;
        } else if (d.getHours() < 22 && d.getHours() >= 18) {
            now = Day.EVENING;
        } else if (d.getHours() <= 5 || d.getHours() >= 22 && d.getHours() < 24) {
            now = Day.MIDNIGHT;
        }
 
        if (d.getHours() > 5 && d.getHours() < 18) {
            timeRate = ((float)d.getHours() - 5 )/ 13;//本来是12个小时,但是为了让太阳露一点出来,+1
        } else {
            if (d.getHours() < 24 && d.getHours() >= 18) {
                timeRate = (float)(d.getHours() - 17) / 12;
            } else {
                timeRate = (float)(d.getHours() + 6) / 12;
            }
        }
    }
 
    private void createBitmaps() {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
 
        cloud1 = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_cloud_01);
        cloud2 = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_cloud_02);
        cloud3 = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_cloud_03);
 
        sun = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_sun);
        sunMorning = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_sun_morning);
        sunNoon = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_sun_noon);
        sunEvening = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_sun_evening);
 
        moon = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_moon);
        star = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_stars);
    }
 
    public void startAnimate(){
        Log.i("ToolbarArcBackground", "timeRate = " + timeRate);
        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
        anim.setDuration(3000);
        //anim.setInterpolator();
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            float temp = timeRate;
 
            int currentGradientColor1 =gradientColor1;
            int currentGradientColor2 =gradientColor2;
 
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //时间段的过渡
                float currentValue = (float) animation.getAnimatedValue();
                timeRate = currentValue * temp;
 
                //由上一个时间段的颜色过渡到下一个时间段的颜色
                ArgbEvaluator argbEvaluator = new ArgbEvaluator();//渐变色计算类
                gradientColor1 = (int) (argbEvaluator.evaluate(currentValue, lastGradientColor1, currentGradientColor1));
                gradientColor2 = (int) (argbEvaluator.evaluate(currentValue, lastGradientColor2, currentGradientColor2));
                invalidate();
            }
        });
        anim.start();
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawGradient(canvas);
        drawCloud(canvas);
        if (now == Day.MIDNIGHT || now == Day.EVENING) {
            drawStar(canvas);
            drawMoon(canvas);
        } else {
            drawSun(canvas);
        }
        drawOval(canvas);
    }
 
    private void drawOval(Canvas canvas) {
 
        Paint ovalPaint = new Paint();
        final Path path = new Path();
        ovalPaint.setColor(Color.WHITE);
        ovalPaint.setAntiAlias(true);
 
        path.moveTo(0, getMeasuredHeight());
 
        path.quadTo(getMeasuredWidth() / 2, getMeasuredHeight() - waveHeight * scale, getMeasuredWidth(), getMeasuredHeight());
 
        path.lineTo(0, getMeasuredHeight());
        path.close();
        canvas.drawPath(path, ovalPaint);
    }
 
    private void drawCloud(Canvas canvas) {
        canvas.drawBitmap(cloud1, cloud1X * scale, cloud1Y * scale, null);
        canvas.drawBitmap(cloud2, cloud2X * scale, cloud2Y * scale, null);
        canvas.drawBitmap(cloud3, cloud3X + (1 - scale) * getMeasuredWidth(), cloud3Y * scale, null);
    }
 
    private void drawStar(Canvas canvas) {
        canvas.drawBitmap(star, 0, 0, null);
    }
 
    private void drawMoon(Canvas canvas) {
        int passed =  (int)(getMeasuredWidth() * timeRate);
        int xpos = passed - moon.getWidth() / 2;
        canvas.drawBitmap(moon, xpos + (1 - scale) * getMeasuredWidth(), -50, null);
    }
 
    private void drawGradient(Canvas canvas) {
        Paint paint = new Paint();
        ArgbEvaluator argbEvaluator = new ArgbEvaluator();//渐变色计算类
        int changedColor1 = (int) (argbEvaluator.evaluate(1 - scale, gradientColor1, ContextCompat.getColor(context, R.color.colorPrimary)));
        int changedColor2 = (int) (argbEvaluator.evaluate(1 - scale, gradientColor2, ContextCompat.getColor(context, R.color.colorPrimary)));
        LinearGradient linearGradient = new LinearGradient(0f, 0f, getMeasuredWidth(), getMeasuredHeight(), changedColor1, changedColor2, Shader.TileMode.CLAMP);
        paint.setShader(linearGradient);
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);
//
//        LinearGradient linearGradient1 = new LinearGradient(0f, 0f, getMeasuredWidth(), getMeasuredHeight(), 0xff00d9ff, 0xff00b0ff, Shader.TileMode.CLAMP);
//        paint.setShader(linearGradient1);
//        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);
    }
 
    private void drawSun(Canvas canvas) {
        Log.e("rate", "timeRate = " + timeRate);
        Log.e("rate", "sun.getWidth() = " + sun.getWidth());
        int passed =  (int)(getMeasuredWidth() * timeRate);
        int xpos = passed - sun.getWidth() / 2;
        if (now == Day.MORNING) {
            canvas.drawBitmap(sunMorning, xpos + (1 - scale) * getMeasuredWidth(), -50, null);
        } else if (now == Day.NOON) {
            canvas.drawBitmap(sunNoon, xpos + (1 - scale) * getMeasuredWidth(), -50, null);
        } else if (now == Day.AFTERNOON) {
            canvas.drawBitmap(sunEvening, xpos + (1 - scale) * getMeasuredWidth(), -50, null);
        }
    }
}Copy the code

Layout activity_main. XML:

<? The XML version = "1.0" encoding = "utf-8"? > <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/main_content" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:id="@+id/appbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true" > <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <com.jcodecraeer.day.ToolbarArcBackground android:id="@+id/toolbarArcBackground" android:layout_width="match_parent" android:layout_height="130dp" /> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="50dp" android:contentInsetEnd="0dp" android:contentInsetLeft="0dp" android:contentInsetRight="0dp" android:contentInsetStart="0dp" app:contentInsetEnd="0dp" app:contentInsetLeft="0dp" app:contentInsetRight="0dp" app:contentInsetStart="0dp" app:layout_collapseMode="pin" > </android.support.v7.widget.Toolbar> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/large_text" /> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout>Copy the code

Use this in MainActivity:

package com.jcodecraeer.day; import android.support.design.widget.AppBarLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.Toolbar; public class MainActivity extends AppCompatActivity { ToolbarArcBackground mToolbarArcBackground; AppBarLayout mAppBarLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); final ActionBar ab = getSupportActionBar(); setTitle(""); mAppBarLayout = (AppBarLayout) findViewById(R.id.appbar); mToolbarArcBackground = (ToolbarArcBackground) findViewById(R.id.toolbarArcBackground); mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { int scrollRange = -1; @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { if (scrollRange == -1) { scrollRange = appBarLayout.getTotalScrollRange(); } float scale = (float) Math.abs(verticalOffset) / scrollRange; mToolbarArcBackground.setScale(1 - scale); }}); getWindow().getDecorView().post(new Runnable() { @Override public void run() { mToolbarArcBackground.startAnimate(); }}); }}Copy the code

Color resources:

<? The XML version = "1.0" encoding = "utf-8"? > <resources> <color name="colorPrimary">#4CAF50</color> <color name="colorPrimaryDark">#4CAF50</color> <color name="colorAccent">#FF4081</color> <color name="toolbar_gradient_1">#ff00d9ff</color> <color name="toolbar_gradient_1_evening">#341c61</color> <color name="toolbar_gradient_1_midnight">#ff416eb2</color> <color name="toolbar_gradient_1_morning">#fff0ecb3</color> <color name="toolbar_gradient_1_noon">#ff00d9ff</color> <color name="toolbar_gradient_1_noon_evening">#ffa976ed</color> <color name="toolbar_gradient_2">#ff00b0ff</color> <color name="toolbar_gradient_2_evening">#1e1918</color> <color name="toolbar_gradient_2_midnight">#ff2a2569</color> <color name="toolbar_gradient_2_morning">#ff00b3ff</color> <color name="toolbar_gradient_2_noon">#ff00b0ff</color> <color name="toolbar_gradient_2_noon_evening">#704343</color> </resources>Copy the code

Image resources on their own decompile it.

Collection (4)
Praise (2)
Step (0)

  • Android Launcher Desktop drawer switch animation 2014-11-30
  • Simulated air quality detector 2015-01-30
  • Android L Animation introduction 2015-03-23
  • A new way to implement Activity conversion animation July 10, 2015
  • Improve Android animation performance through Hardware Layer
  • CSS layered animation allows elements to move along curved paths
  • android animation 2016-07-07
  • Set left and right toggle animation for View and Activity
  • Keyframes: Provides scalable, high quality animation for mobile devices 2017-01-12
  • 【Android】 Give me a Path, return you an animation View 2017-01-04