仿小米天气 日出日落动画

1,756 阅读4分钟
原文链接: blog.csdn.net

先上对比图(上小米,下仿制)

动画效果

代码传送门(无积分可以在下方复制代码)


实现思路

小米的是用圆弧实现的,但我最近学习了贝塞尔曲线就想着拿来练(zhuang)练(zhuang)手(bi).

  1. 先画出太阳的轨迹再给它的画笔添加一个渐变
  2. 再用两个圆画出太阳
  3. 太阳需要一个动画,来升起和降落
  4. 未经过的区域,要用一个半透明的方框蒙住
  5. View 需要有三个方法,分别设置日出,日落,和当前时间

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(mainColor);
        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);
        }
        // 画太阳
        canvas.drawCircle((float) rX, (float)rY, svSunSize*6/5, mSunStrokePaint);
        canvas.drawCircle((float) rX, (float)rY, svSunSize, mSunPaint);
        // 画端点
        mMotionPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(startX, startY, svTrackWidth*2, mMotionPaint);
        canvas.drawCircle(endX, endY, svTrackWidth*2, mMotionPaint);

        canvas.restore();
    }

    /**
     * 设置当前进度,并更新太阳中心点的位置
     * @param t
     */
    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();
    }

    /**
     * 设置当前时间(请先设置日出日落时间)
     */
    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;
            setProgress(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;
            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);
                        if ((Float)val == mProgress){
                            // 保存当前的进度,下一次更新即可以从上次时间运动到当前时间
                            mCurrentProgress = mProgress;
                        }
                    }
                }
            });
            valueAnimator = animator;
        } else {
            valueAnimator.cancel();
            valueAnimator.setFloatValues(mCurrentProgress, mProgress);
        }
        valueAnimator.start();
    }
}

自定义的属性

<?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);