最新消息:欢迎访问Android开发中文站!商务联系QQ:1304524325

仿写Social Steps的ToolBar效果

开发进阶 loading 1173浏览 1评论

编辑推荐:稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识、前端、后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过!

前段时间在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


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>

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

转载请注明:Android开发中文站 » 仿写Social Steps的ToolBar效果

您必须 登录 才能发表评论!

网友最新评论 (1)

  1. 既然是一个经典案例,提供下demo也无妨吧
    155137117392018-06-28 18:56