高仿小米天氣 日出日落動畫控件

先上對比圖(上小米,下仿製)

動畫效果

代碼傳送門(無積分可以在下方複製代碼)


實現思路

小米的是用圓弧實現的,但我最近學習了貝塞爾曲線就想着拿來練(zhuang)練(zhuang)手(bi).

  1. 先畫出太陽的軌跡再給它的畫筆添加一個漸變
  2. 再用兩個圓畫出太陽
  3. 太陽需要一個動畫,來升起和降落
  4. 未經過的區域,要用一個半透明的方框矇住
  5. View 需要有三個方法,分別設置日出,日落,和當前時間

如何使用?(請閱讀最底下README)

SunView代碼

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Shader;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.View;

import com.summer.h5.R;

/**
 * function:仿小米天氣 日出日落動畫控件
 * creator:hw
 * time: 2018/08/20 15:21
 */
public class SunView extends View {

    Paint mPathPaint;
    private int mWidth;
    private int mHeight;
    int mainColor;
    int trackColor;
    private Path mPathPath;
    private Paint mMotionPaint;
    private Path mMotionPath;
    int controlX, controlY;
    float startX, startY;
    float endX, endY;
    private double rX;
    private double rY;
    private int[] mSunrise = new int[2];
    private int[] mSunset = new int[2];
    private Paint mSunPaint;
    private ValueAnimator valueAnimator;
    private float mProgress;
    private Paint mShadePaint;
    private Shader mPathShader;
    private float mCurrentProgress;
    private boolean isDraw = false;
    private DashPathEffect mDashPathEffect;
    private Paint mTextPaint;
    private LinearGradient mBackgroundShader;
    private int sunColor;
    private Paint mSunStrokePaint;
    private float svSunSize;
    private float svTextSize;
    private float textOffset;
    private float svPadding;
    private float svTrackWidth;

    public SunView(Context context) {
        super(context);
        init(null);
    }

    public SunView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public SunView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public SunView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs);
    }

    private void init(AttributeSet attrs) {

        //初始化屬性
        final Context context = getContext();
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SunView);
        // FIXME 這個地方如果xml屬性不給值則拿不到默認值
        mainColor = array.getColor(R.styleable.SunView_svMainColor, 0x67B2FD);
        trackColor = array.getColor(R.styleable.SunView_svTrackColor, 0x67B2FD);
        sunColor = array.getColor(R.styleable.SunView_svSunColor, 0x00D3FE);
        svSunSize = array.getDimension(R.styleable.SunView_svSunRadius, 10);
        svTextSize = array.getDimension(R.styleable.SunView_svTextSize, 18);
        textOffset = array.getDimension(R.styleable.SunView_svTextOffset, 10);
        svPadding = array.getDimension(R.styleable.SunView_svPadding, 10);
        svTrackWidth = array.getDimension(R.styleable.SunView_svTrackWidth, 3);
        array.recycle();

        // 漸變路徑的畫筆
        Paint pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        pathPaint.setColor(mainColor);
        pathPaint.setStyle(Paint.Style.FILL);
        mPathPaint = pathPaint;
        // 漸變路徑
        mPathPath = new Path();
        // 漸變遮罩的畫筆
        Paint shadePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        shadePaint.setColor(Color.parseColor("#B3FFFFFF"));
        shadePaint.setStyle(Paint.Style.FILL);
        mShadePaint = shadePaint;
        // 運動軌跡畫筆
        Paint motionPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        motionPaint.setColor(trackColor);
        motionPaint.setStrokeCap(Paint.Cap.ROUND);
        motionPaint.setStrokeWidth(svTrackWidth);
        motionPaint.setStyle(Paint.Style.STROKE);
        mMotionPaint = motionPaint;
        // 運動軌跡
        mMotionPath = new Path();
        // 太陽畫筆
        Paint sunPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        sunPaint.setColor(sunColor);
        sunPaint.setStyle(Paint.Style.FILL);
        mSunPaint = sunPaint;
        // 太陽邊框畫筆
        Paint sunStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        sunStrokePaint.setColor(Color.WHITE);
        sunStrokePaint.setStyle(Paint.Style.FILL);
        mSunStrokePaint = sunStrokePaint;
        // 日出日落時間畫筆
        Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(trackColor);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setTextSize(svTextSize);
        mTextPaint = textPaint;
        mDashPathEffect = new DashPathEffect(new float[]{6, 12}, 0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        if(!isDraw){
            mWidth = getWidth();
            mHeight = getHeight();
            controlX = mWidth/2;
            controlY = 0-mHeight/2;
            startX = svPadding;
            startY = mHeight-svPadding;
            endX = mWidth-svPadding;
            endY = mHeight-svPadding;
            rX = svPadding;
            rY = mHeight-svPadding;
            // 漸變路徑
            mPathShader = new LinearGradient(mWidth/2, svPadding, mWidth/2, endY,
                    mainColor, Color.WHITE, Shader.TileMode.CLAMP);
            mPathPaint.setShader(mPathShader);
            mPathPath.moveTo(startX, startY);
            mPathPath.quadTo(controlX, controlY, endX, endY);
            // 運動軌跡
            mMotionPath.moveTo(startX, startY);
            mMotionPath.quadTo(controlX, controlY, endX, endY);
            isDraw = true;
        }

        // 按遮擋關係畫
        // 畫漸變
        canvas.drawPath(mPathPath, mPathPaint);
        // 畫已經運動過去的軌跡
        mMotionPaint.setStyle(Paint.Style.STROKE);
        mMotionPaint.setPathEffect(null);
        canvas.drawPath(mMotionPath, mMotionPaint);
        // 畫一個矩形遮住未運動到的漸變和軌跡
        mShadePaint.setShader(mBackgroundShader);
        canvas.drawRect((float) rX, 0, mWidth, mHeight, mShadePaint);
        // 畫一條虛線表示未運動到的軌跡
        mMotionPaint.setPathEffect(mDashPathEffect);
        canvas.drawPath(mMotionPath, mMotionPaint);

        // 畫日出日落文字
        if (mSunrise.length != 0||mSunset.length != 0){
            mTextPaint.setTextAlign(Paint.Align.LEFT);
            canvas.drawText("日出 "+(mSunrise[0]<10? "0"+mSunrise[0]: mSunrise[0])
                    +":"+(mSunrise[1]<10? "0"+mSunrise[1]: mSunrise[1]), startX+textOffset, startY, mTextPaint);
            mTextPaint.setTextAlign(Paint.Align.RIGHT);
            canvas.drawText("日落 "+(mSunset[0]<10? "0"+mSunset[0]: mSunset[0])
                    +":"+(mSunset[1]<10? "0"+mSunset[1]: mSunset[1]), endX-textOffset, endY, mTextPaint);
        }
        
        // 畫端點
        mMotionPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(startX, startY, svTrackWidth*2, mMotionPaint);
        canvas.drawCircle(endX, endY, svTrackWidth*2, mMotionPaint);
        // 畫太陽
        canvas.drawCircle((float) rX, (float)rY, svSunSize*6/5, mSunStrokePaint);
        canvas.drawCircle((float) rX, (float)rY, svSunSize, mSunPaint);

        canvas.restore();
    }

    /**
     * 設置當前進度,並更新太陽中心點的位置
     * @param t 範圍:[0~1]
     */
    private void setProgress(float t){
        mProgress = t;
        rX = startX * Math.pow(1 - t, 2) + 2 * controlX * t * (1 - t) + endX * Math.pow(t, 2);
        rY = startY * Math.pow(1 - t, 2) + 2 * controlY * t * (1 - t) + endY * Math.pow(t, 2);
        // 只更新需要畫的區域
        invalidate((int)rX, 0, (int)(mWidth-svPadding), (int)(mHeight-svPadding));
    }

    /**
     * 設置當前時間(請先設置日出日落時間)
     */
    public void setCurrentTime(int hour, int minute){
        if (mSunrise.length != 0||mSunset.length != 0){
            float p0 = mSunrise[0]*60+mSunrise[1];// 起始分鐘數
            float p1 = hour*60+minute-p0;// 當前時間總分鐘數
            float p2 = mSunset[0]*60+mSunset[1]-p0;// 日落到日出總分鐘數
            float progress = p1/p2;
            mProgress = progress;
            motionAnimation();
        }
    }

    /**
     * 設置日出時間
     */
    public void setSunrise(int hour, int minute){
        mSunrise[0] = hour;
        mSunrise[1] = minute;
    }

    /**
     * 設置日落時間
     */
    public void setSunset(int hour, int minute){
        mSunset[0] = hour;
        mSunset[1] = minute;
    }

    /**
     * 太陽軌跡動畫
      */
    public void motionAnimation(){
        if (valueAnimator == null){
            mCurrentProgress = 0f;
            // 確保太陽不會出界
            if (mProgress<0){
                mProgress=0;
            }
            if (mProgress>1){
                mProgress=1;
            }
            final ValueAnimator animator = ValueAnimator.ofFloat(mCurrentProgress, mProgress);
            animator.setDuration((long) (2500*(mProgress-mCurrentProgress)));
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    Object val = animator.getAnimatedValue();
                    if (val instanceof Float){
                        setProgress((Float) val);
                    }
                }
            });
            valueAnimator = animator;
        } else {
            valueAnimator.cancel();
            valueAnimator.setFloatValues(mCurrentProgress, mProgress);
        }
        valueAnimator.start();
        // 保存當前的進度,下一次調用setCurrentTime()即可以從上次進度運動到當前進度(小米效果)
        mCurrentProgress = mProgress;
    }
}

自定義的屬性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SunView">
        <attr name="svTrackColor" format="color"/>
        <attr name="svSunColor" format="color"/>
        <attr name="svMainColor" format="color"/>
        <attr name="svSunRadius" format="dimension"/>
        <attr name="svTrackWidth" format="dimension"/>
        <attr name="svTextSize" format="dimension"/>
        <attr name="svTextOffset" format="dimension"/>
        <attr name="svPadding" format="dimension"/>
    </declare-styleable>
</resources>

README

1.將atrrs文件放入資源文件夾 values 文件夾下
2.在要使用SunView的xml文件根節點的添加 命名空間(以下是Android Studio 的XML示例)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:sun="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fff">
    <com.summer.h5.View.SunView
        android:id="@+id/sv"
        android:layout_margin="20dp"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        sun:svMainColor="#FE8109" // 背景漸變顏色
        sun:svTrackColor="#FE8109" // 太陽運動軌跡顏色
        sun:svSunColor="#FED300" // 太陽顏色
        sun:svSunRadius="9dp" // 太陽半徑
        sun:svTrackWidth="1dp" // 太陽運動軌跡寬度
        sun:svTextSize="10sp" // 文字大小
        sun:svTextOffset="20dp" // 文字與端點的偏移量
        sun:svPadding="10dp"/> // view的內邊距
</RelativeLayout>
3. 在代碼中調用
       // 找到控件
        sv = findViewById(R.id.sv);
        // 設置日出時間
        sv.setSunrise(05, 39);
        // 設置日落時間
        sv.setSunset(18, 48);
        // 獲取系統 時 分
        Calendar calendar = Calendar.getInstance();
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);
        // 設置當前時間
        sv.setCurrentTime(hour, minute);

轉載需註明原文地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章