/*
* Copyright (c) 2015 LingoChamp Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.liulishuo.magicprogresswidget;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import java.lang.ref.WeakReference;
import cn.dreamtobe.percentsmoothhandler.ISmoothTarget;
import cn.dreamtobe.percentsmoothhandler.SmoothHandler;
/**
* Created by Jacksgong on 12/8/15.
*/
public class MagicProgressCircle extends View implements ISmoothTarget {
// ColorInt
private int startColor;
// ColorInt
private int endColor;
// ColorInt
private int defaultColor;
private int percentEndColor;
private int strokeWidth;
private float percent;
// 用于渐变
private Paint paint;
private SmoothHandler smoothHandler;
public MagicProgressCircle(Context context) {
super(context);
init(context, null);
}
public MagicProgressCircle(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MagicProgressCircle(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MagicProgressCircle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(final Context context, final AttributeSet attrs) {
float defaultPercent = -1;
if (isInEditMode()) {
defaultPercent = 0.6f;
}
do {
final int strokeWdithDefaultValue = (int) (18 * getResources().getDisplayMetrics().density + 0.5f);
if (context == null || attrs == null) {
strokeWidth = strokeWdithDefaultValue;
percent = defaultPercent;
startColor = getResources().getColor(R.color.mpc_start_color);
endColor = getResources().getColor(R.color.mpc_end_color);
defaultColor = getResources().getColor(R.color.mpc_default_color);
break;
}
TypedArray typedArray = null;
try {
typedArray = context.obtainStyledAttributes(attrs, R.styleable.MagicProgressCircle);
percent = typedArray.getFloat(R.styleable.MagicProgressCircle_mpc_percent, defaultPercent);
strokeWidth = (int) typedArray.getDimension(R.styleable.MagicProgressCircle_mpc_stroke_width, strokeWdithDefaultValue);
startColor = typedArray.getColor(R.styleable.MagicProgressCircle_mpc_start_color, getResources().getColor(R.color.mpc_start_color));
endColor = typedArray.getColor(R.styleable.MagicProgressCircle_mpc_end_color, getResources().getColor(R.color.mpc_end_color));
defaultColor = typedArray.getColor(R.styleable.MagicProgressCircle_mpc_default_color, getResources().getColor(R.color.mpc_default_color));
} finally {
if (typedArray != null) {
typedArray.recycle();
}
}
} while (false);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
startPaint = new Paint();
startPaint.setColor(startColor);
startPaint.setAntiAlias(true);
startPaint.setStyle(Paint.Style.FILL);
endPaint = new Paint();
endPaint.setAntiAlias(true);
endPaint.setStyle(Paint.Style.FILL);
refreshDelta();
customColors = new int[]{startColor, percentEndColor, defaultColor, defaultColor};
fullColors = new int[]{startColor, endColor};
emptyColors = new int[]{defaultColor, defaultColor};
customPositions = new float[4];
customPositions[0] = 0;
customPositions[3] = 1;
extremePositions = new float[]{0, 1};
}
private void refreshDelta() {
int endR = (endColor & 0xFF0000) >> 16;
int endG = (endColor & 0xFF00) >> 8;
int endB = (endColor & 0xFF);
this.startR = (startColor & 0xFF0000) >> 16;
this.startG = (startColor & 0xFF00) >> 8;
this.startB = (startColor & 0xFF);
deltaR = endR - startR;
deltaG = endG - startG;
deltaB = endB - startB;
}
/**
* @param percent FloatRange(from = 0.0, to = 1.0)
*/
public void setPercent(float percent) {
percent = Math.min(1, percent);
percent = Math.max(0, percent);
if (smoothHandler != null) {
smoothHandler.commitPercent(percent);
}
if (this.percent != percent) {
this.percent = percent;
invalidate();
}
}
@Override
public void setSmoothPercent(float percent) {
getSmoothHandler().loopSmooth(percent);
}
@Override
public void setSmoothPercent(float percent, long durationMillis) {
getSmoothHandler().loopSmooth(percent, durationMillis);
}
private SmoothHandler getSmoothHandler(){
if (smoothHandler == null) {
smoothHandler = new SmoothHandler(new WeakReference<ISmoothTarget>(this));
}
return smoothHandler;
}
public float getPercent() {
return this.percent;
}
/**
* @param color ColorInt
*/
public void setStartColor(final int color) {
if (this.startColor != color) {
this.startColor = color;
// delta变化
refreshDelta();
// 渐变前部分
customColors[0] = color;
// 前半圆
startPaint.setColor(color);
// 全满时 渐变起点
fullColors[0] = color;
invalidate();
}
}
public int getStartColor() {
return this.startColor;
}
/**
* @param color ColorInt
*/
public void setEndColor(final int color) {
if (this.endColor != color) {
this.endColor = color;
// delta变化
refreshDelta();
// 渐变后部分 动态计算#draw
// 后半圆 需要动态计算#draw,在某些情况下没有
// 全满时 渐变结束
fullColors[1] = color;
invalidate();
}
}
public int getEndColor() {
return this.endColor;
}
/**
* @param color ColorInt
*/
public void setDefaultColor(final int color) {
if (this.defaultColor != color) {
this.defaultColor = color;
// 渐变后半部分
customColors[2] = color;
customColors[3] = color;
// percent = 0
emptyColors[0] = color;
emptyColors[1] = color;
invalidate();
}
}
public int getDefaultColor() {
return this.defaultColor;
}
/**
* @param width px
*/
public void setStrokeWidth(final int width) {
if (this.strokeWidth != width) {
this.strokeWidth = width;
// 画描边的描边变化
paint.setStrokeWidth(width);
// 会影响measure
requestLayout();
}
}
public int getStrokeWidth() {
return this.strokeWidth;
}
private int deltaR, deltaB, deltaG;
private int startR, startB, startG;
private void calculatePercentEndColor(final float percent) {
percentEndColor = ((int) (deltaR * percent + startR) << 16) +
((int) (deltaG * percent + startG) << 8) +
((int) (deltaB * percent + startB)) + 0xFF000000;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
this.rectF.left = getMeasuredWidth() / 2 - strokeWidth / 2;
this.rectF.top = 0;
this.rectF.right = getMeasuredWidth() / 2 + strokeWidth / 2;
this.rectF.bottom = strokeWidth;
}
private Paint startPaint;
private Paint endPaint;
private final RectF rectF = new RectF();
private int[] customColors;
private int[] fullColors;
private int[] emptyColors;
private float[] customPositions;
private float[] extremePositions;
// 目前由于SweepGradient赋值只在构造函数,无法pre allocate & reuse instead
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int restore = canvas.save();
final int cx = getMeasuredWidth() / 2;
final int cy = getMeasuredHeight() / 2;
final int radius = getMeasuredWidth() / 2 - strokeWidth / 2;
float drawPercent = percent;
if (drawPercent > 0.97 && drawPercent < 1) {
// 设计师说这样比较好
drawPercent = 0.97f;
}
// 画渐变圆
canvas.save();
canvas.rotate(-90, cx, cy);
int[] colors;
float[] positions;
if (drawPercent < 1 && drawPercent > 0) {
calculatePercentEndColor(drawPercent);
customColors[1] = percentEndColor;
colors = customColors;
customPositions[1] = drawPercent;
customPositions[2] = drawPercent;
positions = customPositions;
} else if (drawPercent == 1) {
colors = fullColors;
positions = extremePositions;
} else {
// <= 0 || > 1?
colors = emptyColors;
positions = extremePositions;
}
final SweepGradient sweepGradient = new SweepGradient(getMeasuredWidth() / 2, getMeasuredHeight() / 2, colors, positions);
paint.setShader(sweepGradient);
canvas.drawCircle(cx, cy, radius, paint);
canvas.restore();
if (drawPercent > 0) {
// 绘制结束的半圆
if (drawPercent < 1) {
canvas.save();
endPaint.setColor(percentEndColor);
canvas.rotate((int) Math.floor(360.0f * drawPercent) - 1, cx, cy);
canvas.drawArc(rectF, -90f, 180f, true, endPaint);
canvas.restore();
}
canvas.save();
// 绘制开始的半圆
canvas.drawArc(rectF, 90f, 180f, true, startPaint);
canvas.restore();
}
canvas.restoreToCount(restore);
}
}