先上對比圖(上小米,下仿製)
動畫效果
代碼傳送門(無積分可以在下方複製代碼)
實現思路
小米的是用圓弧實現的,但我最近學習了貝塞爾曲線就想着拿來練(zhuang)練(zhuang)手(bi).
- 先畫出太陽的軌跡再給它的畫筆添加一個漸變
- 再用兩個圓畫出太陽
- 太陽需要一個動畫,來升起和降落
- 未經過的區域,要用一個半透明的方框矇住
- 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);
轉載需註明原文地址