仿写Social Steps的ToolBar效果
前段时间在medium上看到一篇比较有意思的文章Toolbar Delight。该篇文章讲解了如何实现下面这种效果:

gif效果不好,想看清晰的版本请看原始文章的视频。
文章虽好,但是代码不全,有些细节作者其实也没有透露。于是我大致看了之后决定自己实现一个类似的效果,相似程度95以上吧。
其实这种还是很简单的,都是些细节问题,大致可以分解为:
-
从左到右边的渐变,这个很简单。
-
滚动的时候弧度随着 AppBarLayout 的 verticalOffset 发生变化,当折叠的时候,颜色逐渐过渡到colorPrimary,同时云彩也在折叠的时候往边界跑。
-
不同时间颜色是不一样的,太阳或者月亮的位置也尽量模拟真实世界。这个不难,把一天的时间分段处理就好了。
-
当打开界面的时候,有一个从上一个时间段状态过渡到当前状态的动画。我这里的实现效果跟原文略有区别,但是要做到跟文章完全吻合也很简单。
至于太阳,星星,云彩,都是bitmap,反编译Social Steps得到的。
好吧,编不下去了,直接看我最终实现的效果:

以上是晚上19.44的效果,其它时间段就不一一上图了。
大部分效果都是在一个叫做ToolbarArcBackground的自定义view中实现的:
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);
- }
- }
- }
布局activity_main.xml:
- <?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>
在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();
- }
- });
- }
- }
颜色资源:
- <?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>
图片资源就自己反编译吧。
浙公网安备 33010602011771号