仿写Social Steps的ToolBar效果

 

 
 

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

Untitled.gif

gif效果不好,想看清晰的版本请看原始文章的视频。

文章虽好,但是代码不全,有些细节作者其实也没有透露。于是我大致看了之后决定自己实现一个类似的效果,相似程度95以上吧。

其实这种还是很简单的,都是些细节问题,大致可以分解为:

  1. 从左到右边的渐变,这个很简单。

  2. 滚动的时候弧度随着 AppBarLayout 的 verticalOffset 发生变化,当折叠的时候,颜色逐渐过渡到colorPrimary,同时云彩也在折叠的时候往边界跑。

  3. 不同时间颜色是不一样的,太阳或者月亮的位置也尽量模拟真实世界。这个不难,把一天的时间分段处理就好了。

  4. 当打开界面的时候,有一个从上一个时间段状态过渡到当前状态的动画。我这里的实现效果跟原文略有区别,但是要做到跟文章完全吻合也很简单。

至于太阳,星星,云彩,都是bitmap,反编译Social Steps得到的。

好吧,编不下去了,直接看我最终实现的效果:

Untitled.gif

以上是晚上19.44的效果,其它时间段就不一一上图了。

大部分效果都是在一个叫做ToolbarArcBackground的自定义view中实现的:

ToolbarArcBackground.java

  1. public class ToolbarArcBackground extends View {
  2.     private float scale = 1;
  3.  
  4.     private float timeRate;
  5.  
  6.     private int gradientColor1 = 0xff4CAF50;
  7.     private int gradientColor2 = 0xFF0E3D10;
  8.     private int lastGradientColor1 = 0xff4CAF50;
  9.     private int lastGradientColor2 = 0xFF0E3D10;
  10.     private Context context;
  11.  
  12.     private Bitmap sun;
  13.     private Bitmap sunMorning;
  14.     private Bitmap sunNoon;
  15.     private Bitmap sunEvening;
  16.  
  17.     private Bitmap cloud1;
  18.     private Bitmap cloud2;
  19.     private Bitmap cloud3;
  20.  
  21.     private Bitmap moon;
  22.     private Bitmap star;
  23.  
  24.     private int cloud1X = 50;
  25.     private int cloud2X = 450;
  26.     private int cloud3X = 850;
  27.  
  28.     int waveHeight = 60;
  29.  
  30.     private int cloud1Y = waveHeight + 150;
  31.     private int cloud2Y = waveHeight + 120;
  32.     private int cloud3Y = waveHeight + 20;
  33.  
  34.  
  35.  
  36.     private int sunHeight;
  37.  
  38.     private Day now = Day.MORNING;
  39.  
  40.     enum Day {
  41.         MORNING, NOON, AFTERNOON, EVENING,
  42.         MIDNIGHT
  43.     }
  44.  
  45.     public ToolbarArcBackground(Context context) {
  46.         super(context);
  47.         this.context = context;
  48.         init();
  49.     }
  50.  
  51.     public ToolbarArcBackground(Context context, AttributeSet attrs) {
  52.         super(context, attrs);
  53.         this.context = context;
  54.         init();
  55.     }
  56.  
  57.     public ToolbarArcBackground(Context context, AttributeSet attrs, int defStyleAttr) {
  58.         super(context, attrs, defStyleAttr);
  59.         this.context = context;
  60.         init();
  61.     }
  62.  
  63.     public void setScale(float scale) {
  64.         this.scale = scale;
  65.         invalidate();
  66.     }
  67.  
  68.     private void init() {
  69.         calculateTimeLine();
  70.         createBitmaps();
  71.         initGradient();
  72.     }
  73.  
  74.     private void initGradient(){
  75.         switch (now) {
  76.             case MORNING:
  77.                 lastGradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_midnight);
  78.                 lastGradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_midnight);
  79.  
  80.                 gradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_morning);
  81.                 gradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_morning);
  82.                 break;
  83.             case NOON:
  84.                 lastGradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_morning);
  85.                 lastGradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_morning);
  86.  
  87.                 gradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_noon);
  88.                 gradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_noon);
  89.                 break;
  90.             case AFTERNOON:
  91.                 lastGradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_noon);
  92.                 lastGradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_noon);
  93.  
  94.                 gradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_noon_evening);
  95.                 gradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_noon_evening);
  96.                 break;
  97.             case EVENING:
  98.                 lastGradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_noon_evening);
  99.                 lastGradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_noon_evening);
  100.  
  101.                 gradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_evening);
  102.                 gradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_evening);
  103.                 break;
  104.             case MIDNIGHT:
  105.                 lastGradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_evening);
  106.                 lastGradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_evening);
  107.  
  108.                 gradientColor1 = ContextCompat.getColor(context, R.color.toolbar_gradient_1_midnight);
  109.                 gradientColor2 = ContextCompat.getColor(context, R.color.toolbar_gradient_2_midnight);
  110.                 break;
  111.         }
  112.     }
  113.  
  114.     private void calculateTimeLine() {
  115.         Date d = new Date();
  116.         if (d.getHours() > 5 && d.getHours() < 11) {
  117.             now = Day.MORNING;
  118.         } else if (d.getHours() < 13 && d.getHours() >= 11) {
  119.             now = Day.NOON;
  120.         } else if (d.getHours() < 18 && d.getHours() >= 13) {
  121.             now = Day.AFTERNOON;
  122.         } else if (d.getHours() < 22 && d.getHours() >= 18) {
  123.             now = Day.EVENING;
  124.         } else if (d.getHours() <= 5 || d.getHours() >= 22 && d.getHours() < 24) {
  125.             now = Day.MIDNIGHT;
  126.         }
  127.  
  128.         if (d.getHours() > 5 && d.getHours() < 18) {
  129.             timeRate = ((float)d.getHours() - 5 )/ 13;//本来是12个小时,但是为了让太阳露一点出来,+1
  130.         } else {
  131.             if (d.getHours() < 24 && d.getHours() >= 18) {
  132.                 timeRate = (float)(d.getHours() - 17) / 12;
  133.             } else {
  134.                 timeRate = (float)(d.getHours() + 6) / 12;
  135.             }
  136.         }
  137.     }
  138.  
  139.     private void createBitmaps() {
  140.         final BitmapFactory.Options options = new BitmapFactory.Options();
  141.         options.inPreferredConfig = Bitmap.Config.RGB_565;
  142.  
  143.         cloud1 = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_cloud_01);
  144.         cloud2 = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_cloud_02);
  145.         cloud3 = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_cloud_03);
  146.  
  147.         sun = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_sun);
  148.         sunMorning = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_sun_morning);
  149.         sunNoon = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_sun_noon);
  150.         sunEvening = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_sun_evening);
  151.  
  152.         moon = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_moon);
  153.         star = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.bg_stars);
  154.     }
  155.  
  156.     public void startAnimate(){
  157.         Log.i("ToolbarArcBackground", "timeRate = " + timeRate);
  158.         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
  159.         anim.setDuration(3000);
  160.         //anim.setInterpolator();
  161.         anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  162.             float temp = timeRate;
  163.  
  164.             int currentGradientColor1 =gradientColor1;
  165.             int currentGradientColor2 =gradientColor2;
  166.  
  167.             @Override
  168.             public void onAnimationUpdate(ValueAnimator animation) {
  169.                 //时间段的过渡
  170.                 float currentValue = (float) animation.getAnimatedValue();
  171.                 timeRate = currentValue * temp;
  172.  
  173.                 //由上一个时间段的颜色过渡到下一个时间段的颜色
  174.                 ArgbEvaluator argbEvaluator = new ArgbEvaluator();//渐变色计算类
  175.                 gradientColor1 = (int) (argbEvaluator.evaluate(currentValue, lastGradientColor1, currentGradientColor1));
  176.                 gradientColor2 = (int) (argbEvaluator.evaluate(currentValue, lastGradientColor2, currentGradientColor2));
  177.                 invalidate();
  178.             }
  179.         });
  180.         anim.start();
  181.     }
  182.  
  183.     @Override
  184.     protected void onDraw(Canvas canvas) {
  185.         super.onDraw(canvas);
  186.         drawGradient(canvas);
  187.         drawCloud(canvas);
  188.         if (now == Day.MIDNIGHT || now == Day.EVENING) {
  189.             drawStar(canvas);
  190.             drawMoon(canvas);
  191.         } else {
  192.             drawSun(canvas);
  193.         }
  194.         drawOval(canvas);
  195.     }
  196.  
  197.     private void drawOval(Canvas canvas) {
  198.  
  199.         Paint ovalPaint = new Paint();
  200.         final Path path = new Path();
  201.         ovalPaint.setColor(Color.WHITE);
  202.         ovalPaint.setAntiAlias(true);
  203.  
  204.         path.moveTo(0, getMeasuredHeight());
  205.  
  206.         path.quadTo(getMeasuredWidth() / 2, getMeasuredHeight() - waveHeight * scale, getMeasuredWidth(), getMeasuredHeight());
  207.  
  208.         path.lineTo(0, getMeasuredHeight());
  209.         path.close();
  210.         canvas.drawPath(path, ovalPaint);
  211.     }
  212.  
  213.     private void drawCloud(Canvas canvas) {
  214.         canvas.drawBitmap(cloud1, cloud1X * scale, cloud1Y * scale, null);
  215.         canvas.drawBitmap(cloud2, cloud2X * scale, cloud2Y * scale, null);
  216.         canvas.drawBitmap(cloud3, cloud3X + (1 - scale) * getMeasuredWidth(), cloud3Y * scale, null);
  217.     }
  218.  
  219.     private void drawStar(Canvas canvas) {
  220.         canvas.drawBitmap(star, 0, 0, null);
  221.     }
  222.  
  223.     private void drawMoon(Canvas canvas) {
  224.         int passed =  (int)(getMeasuredWidth() * timeRate);
  225.         int xpos = passed - moon.getWidth() / 2;
  226.         canvas.drawBitmap(moon, xpos + (1 - scale) * getMeasuredWidth(), -50, null);
  227.     }
  228.  
  229.     private void drawGradient(Canvas canvas) {
  230.         Paint paint = new Paint();
  231.         ArgbEvaluator argbEvaluator = new ArgbEvaluator();//渐变色计算类
  232.         int changedColor1 = (int) (argbEvaluator.evaluate(1 - scale, gradientColor1, ContextCompat.getColor(context, R.color.colorPrimary)));
  233.         int changedColor2 = (int) (argbEvaluator.evaluate(1 - scale, gradientColor2, ContextCompat.getColor(context, R.color.colorPrimary)));
  234.         LinearGradient linearGradient = new LinearGradient(0f, 0f, getMeasuredWidth(), getMeasuredHeight(), changedColor1, changedColor2, Shader.TileMode.CLAMP);
  235.         paint.setShader(linearGradient);
  236.         canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);
  237. //
  238. //        LinearGradient linearGradient1 = new LinearGradient(0f, 0f, getMeasuredWidth(), getMeasuredHeight(), 0xff00d9ff, 0xff00b0ff, Shader.TileMode.CLAMP);
  239. //        paint.setShader(linearGradient1);
  240. //        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);
  241.     }
  242.  
  243.     private void drawSun(Canvas canvas) {
  244.         Log.e("rate", "timeRate = " + timeRate);
  245.         Log.e("rate", "sun.getWidth() = " + sun.getWidth());
  246.         int passed =  (int)(getMeasuredWidth() * timeRate);
  247.         int xpos = passed - sun.getWidth() / 2;
  248.         if (now == Day.MORNING) {
  249.             canvas.drawBitmap(sunMorning, xpos + (1 - scale) * getMeasuredWidth(), -50, null);
  250.         } else if (now == Day.NOON) {
  251.             canvas.drawBitmap(sunNoon, xpos + (1 - scale) * getMeasuredWidth(), -50, null);
  252.         } else if (now == Day.AFTERNOON) {
  253.             canvas.drawBitmap(sunEvening, xpos + (1 - scale) * getMeasuredWidth(), -50, null);
  254.         }
  255.     }
  256. }

布局activity_main.xml:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"
  4.     android:id="@+id/main_content"
  5.     android:layout_width="match_parent"
  6.     android:layout_height="match_parent">
  7.  
  8.     <android.support.design.widget.AppBarLayout
  9.         android:id="@+id/appbar"
  10.         android:layout_width="match_parent"
  11.         android:layout_height="wrap_content"
  12.         android:fitsSystemWindows="true"
  13.         >
  14.  
  15.         <android.support.design.widget.CollapsingToolbarLayout
  16.             android:id="@+id/collapsing_toolbar"
  17.             android:layout_width="match_parent"
  18.             android:layout_height="wrap_content"
  19.             android:fitsSystemWindows="true"
  20.             app:layout_scrollFlags="scroll|exitUntilCollapsed">
  21.             <com.jcodecraeer.day.ToolbarArcBackground
  22.                 android:id="@+id/toolbarArcBackground"
  23.                 android:layout_width="match_parent"
  24.                 android:layout_height="130dp" />
  25.             <android.support.v7.widget.Toolbar
  26.                 android:id="@+id/toolbar"
  27.                 android:layout_width="match_parent"
  28.                 android:layout_height="50dp"
  29.                 android:contentInsetEnd="0dp"
  30.                 android:contentInsetLeft="0dp"
  31.                 android:contentInsetRight="0dp"
  32.                 android:contentInsetStart="0dp"
  33.                 app:contentInsetEnd="0dp"
  34.                 app:contentInsetLeft="0dp"
  35.                 app:contentInsetRight="0dp"
  36.                 app:contentInsetStart="0dp"
  37.                 app:layout_collapseMode="pin"
  38.                 >
  39.  
  40.             </android.support.v7.widget.Toolbar>
  41.         </android.support.design.widget.CollapsingToolbarLayout>
  42.  
  43.     </android.support.design.widget.AppBarLayout>
  44.  
  45.     <android.support.v4.widget.NestedScrollView
  46.         android:layout_width="match_parent"
  47.         android:layout_height="match_parent"
  48.         app:layout_behavior="@string/appbar_scrolling_view_behavior"
  49.     >
  50.  
  51.         <TextView
  52.             android:layout_width="wrap_content"
  53.             android:layout_height="wrap_content"
  54.  
  55.             android:text="@string/large_text" />
  56.  
  57.     </android.support.v4.widget.NestedScrollView>
  58.  
  59. </android.support.design.widget.CoordinatorLayout>

 在MainActivity中这样使用:

  1. package com.jcodecraeer.day;
  2.  
  3. import android.support.design.widget.AppBarLayout;
  4. import android.support.v7.app.ActionBar;
  5. import android.support.v7.app.AppCompatActivity;
  6. import android.os.Bundle;
  7. import android.support.v7.widget.Toolbar;
  8.  
  9. public class MainActivity extends AppCompatActivity {
  10.     ToolbarArcBackground mToolbarArcBackground;
  11.     AppBarLayout mAppBarLayout;
  12.  
  13.     @Override
  14.     protected void onCreate(Bundle savedInstanceState) {
  15.         super.onCreate(savedInstanceState);
  16.         setContentView(R.layout.activity_main);
  17.  
  18.         Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
  19.         setSupportActionBar(toolbar);
  20.         final ActionBar ab = getSupportActionBar();
  21.         setTitle("");
  22.  
  23.         mAppBarLayout = (AppBarLayout) findViewById(R.id.appbar);
  24.         mToolbarArcBackground = (ToolbarArcBackground) findViewById(R.id.toolbarArcBackground);
  25.         mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
  26.             int scrollRange = -1;
  27.             @Override
  28.             public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
  29.                 if (scrollRange == -1) {
  30.                     scrollRange = appBarLayout.getTotalScrollRange();
  31.                 }
  32.  
  33.                 float scale = (float) Math.abs(verticalOffset) / scrollRange;
  34.  
  35.  
  36.                 mToolbarArcBackground.setScale(1 - scale);
  37.  
  38.             }
  39.         });
  40.         getWindow().getDecorView().post(new Runnable() {
  41.             @Override
  42.             public void run() {
  43.                 mToolbarArcBackground.startAnimate();
  44.             }
  45.         });
  46.     }
  47. }

颜色资源:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3.     <color name="colorPrimary">#4CAF50</color>
  4.     <color name="colorPrimaryDark">#4CAF50</color>
  5.     <color name="colorAccent">#FF4081</color>
  6.  
  7.     <color name="toolbar_gradient_1">#ff00d9ff</color>
  8.     <color name="toolbar_gradient_1_evening">#341c61</color>
  9.     <color name="toolbar_gradient_1_midnight">#ff416eb2</color>
  10.     <color name="toolbar_gradient_1_morning">#fff0ecb3</color>
  11.     <color name="toolbar_gradient_1_noon">#ff00d9ff</color>
  12.     <color name="toolbar_gradient_1_noon_evening">#ffa976ed</color>
  13.     <color name="toolbar_gradient_2">#ff00b0ff</color>
  14.     <color name="toolbar_gradient_2_evening">#1e1918</color>
  15.     <color name="toolbar_gradient_2_midnight">#ff2a2569</color>
  16.     <color name="toolbar_gradient_2_morning">#ff00b3ff</color>
  17.     <color name="toolbar_gradient_2_noon">#ff00b0ff</color>
  18.     <color name="toolbar_gradient_2_noon_evening">#704343</color>
  19. </resources>

图片资源就自己反编译吧。

posted @ 2018-11-21 14:52  学无止境,勤思多练  阅读(229)  评论(0)    收藏  举报