效果圖如下:
一。view的組成
看到上圖效果举庶,大概可以確定這個view由三部分組成
1.繞一圈的正方形
2.中間的圖形
3.移動的正方形
1.繪制最外層的正方形
最外層是由12個正方形組成添祸,并且從1到12緩慢增長,緩慢增長的過程箕别,我是用ValueAnimator控制,同時可以看出這個動畫串稀,是由兩部分組成除抛,一部是正方形的垂直下落動畫,另一部分則是最外圈正方形增長動畫,具體代碼如下:
mValueAnimator = ValueAnimator.ofInt(0,13);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator)
{
//? ? ? ? ? ? ? ? if (++progress >= max) {
//? ? ? ? ? ? ? ? ? ? progress = max;
//? ? ? ? ? ? ? ? ? ? bar_null.setProgress(progress);
//? ? ? ? ? ? ? ? ? ? stopProgress();
//? ? ? ? ? ? ? ? ? ? return;
//? ? ? ? ? ? ? ? }
setProgress((Integer) valueAnimator.getAnimatedValue());
Log.e(" valueAnimator ","" +? valueAnimator.getAnimatedValue());
}
});
mValueAnimator.setDuration(2500);
mValueAnimator.setRepeatMode(ValueAnimator.RESTART);
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.start();
mValueAnimator.addListener(new Animator.AnimatorListener()
{
@Override
public void onAnimationStart(Animator animator)
{
}
@Override
public void onAnimationEnd(Animator animator)
{
}
@Override
public void onAnimationCancel(Animator animator)
{
}
@Override
public void onAnimationRepeat(Animator animator)
{
switch (currentStatus){
case SQUARE:
currentStatus = CIRCLE;
break;
case CIRCLE:
currentStatus = SQUARE;
break;
}
}
});
繪制最外層的正方形是用? mCanvas.rotate(30f, mCenterX, mCenterY); 方法進(jìn)行旋轉(zhuǎn),總共一圈12個正方形母截,360/12=30到忽,每一圈12個正方形每一次旋轉(zhuǎn)30度
繪制正方形代碼如下:
int count = 0;
Log.e("mpercent",percent + "");
// 1.1 當(dāng)前進(jìn)度
while (count++ < percent) {
//mCanvas.drawCircle(mCenterX, mCenterY - outerRadius + dotRadius, dotRadius, mPaint);
mCanvas.drawRect(mCenterX-size/2,getHeight()-size,mCenterX+size/2,getHeight(),mPaint);
//? ? mCanvas.drawRect(mCenterX-50/2,0,mCenterX+50/2,50,mPaint);
mCanvas.rotate(30f, mCenterX, mCenterY); //3.6f
Log.e(" count ",count+"");
Log.e(" percent ",percent+"");
}
// 1.2 未完成進(jìn)度
mPaint.setColor(dotBgColor);
count--;
while (count++ < 12) {
mCanvas.drawCircle(mCenterX, mCenterY - outerRadius + dotRadius, dotRadius, mPaint);
// mCanvas.drawRect(mCenterX-50/2,0,mCenterX+50/2,50,mPaint);
mCanvas.rotate(30f, mCenterX, mCenterY);
}
這里每一次重繪需要旋轉(zhuǎn)12次,按進(jìn)度的變化清寇,不斷繪制紅色矩形喘漏,剩余的用白色替代,不然會出現(xiàn)角度錯亂的現(xiàn)象.算出每一次旋轉(zhuǎn)的百分比:
percent = progress * 12 / progressMax;
2.繪制中間圖形华烟,這個比較容易了陷遮,直接調(diào)用 mCanvas.drawBitmap方法,先算出中心點(diǎn)的坐標(biāo),獲取bitmap具體代碼如下:
mCenterX = getWidth() / 2f;
mCenterY = getHeight() / 2f;
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),R.mipmap.ic_launcher);
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
mCanvas.drawBitmap(bitmap,mCenterX - bitmapWidth/2,mCenterY-bitmapHeight/2,mPaint);
3.繪制移動的矩形垦江,這個不斷的去改變矩形的左上和右下的坐標(biāo)帽馋,每一次重繪時記得清空畫布,不然原來圖形會存在,按照百分比去不斷增加剩余的高度搅方,具體代碼如下
int movePercent = (int) ((getHeight()-size-mCenterY-bitmapHeight/2) * progress / 12);
mCanvas.drawRect(mCenterX-size/2,mCenterY+bitmapHeight/2 + movePercent,mCenterX+size/2,mCenterY+bitmapHeight/2+size + movePercent,squarePaint);
下面貼上完整代碼
public class CircleProgressBar extends View
{
/**
* 當(dāng)前進(jìn)度
*/
private int progress;
/**
* 當(dāng)前百分比
*/
private int percent;
/**
* 最大進(jìn)度
*/
private int progressMax;
private int dotColor;
private int dotBgColor;
private int showMode;
public static final int SHOW_MODE_NULL? ? ? = 0;
public static final int SHOW_MODE_PERCENT? = 1;
public static final int SHOW_MODE_ALL? ? ? = 2;
private float percentTextSize;
private int percentTextColor;
private boolean isPercentFontSystem;
private int percentThinPadding;
private String unitText;
private float unitTextSize;
private int unitTextColor;
private int unitTextAlignMode;
public static final int UNIT_TEXT_ALIGN_MODE_DEFAULT = 0;
public static final int UNIT_TEXT_ALIGN_MODE_CN = 1;
public static final int UNIT_TEXT_ALIGN_MODE_EN? ? ? = 2;
private String buttonText;
private float buttonTextSize;
private int buttonTextColor;
private int buttonBgColor;
private int buttonClickColor;
private int buttonClickBgColor;
private float buttonTopOffset;
private Paint mPaint;
private float mSin_1; // sin(1°)
private float mCenterX;
private float mCenterY;
private Canvas mCanvas;
private Bitmap mBitmap;
private Xfermode mClearCanvasXfermode;
private Xfermode mPercentThinXfermode;
private Context mContext;
private Paint squarePaint;
private ValueAnimator mValueAnimator;
private final int SQUARE = 1005;
private final int CIRCLE = 1006;
private int currentStatus = 0;
public CircleProgressBar(Context context)
{
this(context,null);
}
public CircleProgressBar(Context context, AttributeSet attrs)
{
this(context, attrs,0);
}
public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
this.mContext = context;
currentStatus = SQUARE;
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CircleDotProgressBar);
// 獲取自定義屬性值或默認(rèn)值
progressMax = ta.getInteger(R.styleable.CircleDotProgressBar_progressMax, 12);
dotColor = ta.getColor(R.styleable.CircleDotProgressBar_dotColor, Color.RED);
dotBgColor = ta.getColor(R.styleable.CircleDotProgressBar_dotBgColor, Color.WHITE);
showMode = ta.getInt(R.styleable.CircleDotProgressBar_showMode, SHOW_MODE_PERCENT);
if (showMode != SHOW_MODE_NULL) {
percentTextSize = ta.getDimension(R.styleable.CircleDotProgressBar_percentTextSize, dp2px(30));
percentTextColor = ta.getInt(R.styleable.CircleDotProgressBar_percentTextColor, Color.WHITE);
isPercentFontSystem = ta.getBoolean(R.styleable.CircleDotProgressBar_isPercentFontSystem, false);
percentThinPadding = ta.getInt(R.styleable.CircleDotProgressBar_percentThinPadding, 0);
unitText = ta.getString(R.styleable.CircleDotProgressBar_unitText);
unitTextSize = ta.getDimension(R.styleable.CircleDotProgressBar_unitTextSize, percentTextSize);
unitTextColor = ta.getInt(R.styleable.CircleDotProgressBar_unitTextColor, Color.WHITE);
unitTextAlignMode = ta.getInt(R.styleable.CircleDotProgressBar_unitTextAlignMode, UNIT_TEXT_ALIGN_MODE_DEFAULT);
if (unitText == null) {
unitText = "%";
}
}
if (showMode == SHOW_MODE_ALL) {
buttonText = ta.getString(R.styleable.CircleDotProgressBar_buttonText);
buttonTextSize = ta.getDimension(R.styleable.CircleDotProgressBar_buttonTextSize, dp2px(15));
buttonTextColor = ta.getInt(R.styleable.CircleDotProgressBar_buttonTextColor, Color.GRAY);
buttonBgColor = ta.getInt(R.styleable.CircleDotProgressBar_buttonBgColor, Color.WHITE);
buttonClickColor = ta.getInt(R.styleable.CircleDotProgressBar_buttonClickColor, buttonBgColor);
buttonClickBgColor = ta.getInt(R.styleable.CircleDotProgressBar_buttonClickBgColor, buttonTextColor);
buttonTopOffset = ta.getDimension(R.styleable.CircleDotProgressBar_buttonTopOffset, dp2px(15));
if (buttonText == null) {
buttonText = context.getString(R.string.CircleDotProgressBar_speed_up_one_key);
}
}
ta.recycle();
// 其他準(zhǔn)備工作
mSin_1 = (float) Math.sin(Math.toRadians(1)); // 求sin(1°)。角度需轉(zhuǎn)換成弧度
mPaint = new Paint();
mPaint.setAntiAlias(true); // 消除鋸齒
squarePaint = new Paint();
squarePaint.setAntiAlias(true); // 消除鋸齒
squarePaint.setColor(Color.RED);
//? ? ? ? mPercentTypeface = isPercentFontSystem ? Typeface.DEFAULT
//? ? ? ? ? ? ? ? : Typeface.createFromAsset(context.getAssets(), "fonts/HelveticaNeueLTPro.ttf");
mClearCanvasXfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
if (percentThinPadding != 0) {
mPercentThinXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
}
mValueAnimator = ValueAnimator.ofInt(0,13);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator)
{
//? ? ? ? ? ? ? ? if (++progress >= max) {
//? ? ? ? ? ? ? ? ? ? progress = max;
//? ? ? ? ? ? ? ? ? ? bar_null.setProgress(progress);
//? ? ? ? ? ? ? ? ? ? stopProgress();
//? ? ? ? ? ? ? ? ? ? return;
//? ? ? ? ? ? ? ? }
setProgress((Integer) valueAnimator.getAnimatedValue());
Log.e(" valueAnimator ","" +? valueAnimator.getAnimatedValue());
}
});
mValueAnimator.setDuration(2500);
mValueAnimator.setRepeatMode(ValueAnimator.RESTART);
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.start();
mValueAnimator.addListener(new Animator.AnimatorListener()
{
@Override
public void onAnimationStart(Animator animator)
{
}
@Override
public void onAnimationEnd(Animator animator)
{
}
@Override
public void onAnimationCancel(Animator animator)
{
}
@Override
public void onAnimationRepeat(Animator animator)
{
switch (currentStatus){
case SQUARE:
currentStatus = CIRCLE;
break;
case CIRCLE:
currentStatus = SQUARE;
break;
}
}
});
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
private int size = 30;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 繪制外圍圓點(diǎn)進(jìn)度
//? ? ? ? drawCircleDot(mCanvas);
// 先清除上次繪制的
mPaint.setXfermode(mClearCanvasXfermode);
mCanvas.drawPaint(mPaint);
mPaint.setXfermode(null);
mCenterX = getWidth() / 2f;
mCenterY = getHeight() / 2f;
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),R.mipmap.ic_launcher);
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
mCanvas.drawBitmap(bitmap,mCenterX - bitmapWidth/2,mCenterY-bitmapHeight/2,mPaint);
Log.e(" currentStatus ", currentStatus + "");
// mCanvas.drawRect(mCenterX-size/2,getHeight()-size,mCenterX+size/2,getHeight(),mPaint);
//? mCanvas.drawRect(mCenterX-size/2,mCenterY+bitmapHeight/2,mCenterX+size/2,mCenterY+bitmapHeight/2+size,mPaint);
if (percent == 12){
mCanvas.drawRect(mCenterX-size/2,getHeight()-size,mCenterX+size/2,getHeight(),mPaint);
}
switch (currentStatus){
case SQUARE:
int movePercent = (int) ((getHeight()-size-mCenterY-bitmapHeight/2) * progress / 12);
mCanvas.drawRect(mCenterX-size/2,mCenterY+bitmapHeight/2 + movePercent,mCenterX+size/2,mCenterY+bitmapHeight/2+size + movePercent,squarePaint);
// mCanvas.drawRect(mCenterX-size/2,mCenterY+bitmapHeight + movePercent ,mCenterX+size/2,mCenterY+bitmapHeight+size + movePercent,squarePaint);
break;
case CIRCLE:
// 計(jì)算圓點(diǎn)半徑
float outerRadius = (getWidth() < getHeight() ? getWidth() : getHeight()) / 2f;
mCenterX = getWidth() / 2f;
mCenterY = getHeight() / 2f;
// outerRadius = innerRadius + dotRadius
// sin((360°/200)/2) = sin(0.9°) = dotRadius / innerRadius;
// 為了讓圓點(diǎn)飽滿一些绽族,把角度0.9°增加0.1°到1°
float dotRadius = mSin_1 * outerRadius / (1 + mSin_1);
// 畫進(jìn)度
mPaint.setColor(dotColor);
mPaint.setStyle(Paint.Style.FILL);
int count = 0;
Log.e("mpercent",percent + "");
// 1.1 當(dāng)前進(jìn)度
while (count++ < percent) {
//mCanvas.drawCircle(mCenterX, mCenterY - outerRadius + dotRadius, dotRadius, mPaint);
mCanvas.drawRect(mCenterX-size/2,getHeight()-size,mCenterX+size/2,getHeight(),mPaint);
//? ? mCanvas.drawRect(mCenterX-50/2,0,mCenterX+50/2,50,mPaint);
mCanvas.rotate(30f, mCenterX, mCenterY); //3.6f
Log.e(" count ",count+"");
Log.e(" percent ",percent+"");
}
// 1.2 未完成進(jìn)度
mPaint.setColor(dotBgColor);
count--;
while (count++ < 12) {
mCanvas.drawCircle(mCenterX, mCenterY - outerRadius + dotRadius, dotRadius, mPaint);
// mCanvas.drawRect(mCenterX-50/2,0,mCenterX+50/2,50,mPaint);
mCanvas.rotate(30f, mCenterX, mCenterY);
}
break;
}
canvas.drawBitmap(mBitmap, 0, 0, null);
}
/**
* 繪制圓點(diǎn)進(jìn)度
* @param canvas 畫布
*/
private void drawCircleDot(Canvas canvas) {
// 先清除上次繪制的
mPaint.setXfermode(mClearCanvasXfermode);
mCanvas.drawPaint(mPaint);
mPaint.setXfermode(null);
// 計(jì)算圓點(diǎn)半徑
float outerRadius = (getWidth() < getHeight() ? getWidth() : getHeight()) / 2f;
mCenterX = getWidth() / 2f;
mCenterY = getHeight() / 2f;
// outerRadius = innerRadius + dotRadius
// sin((360°/200)/2) = sin(0.9°) = dotRadius / innerRadius;
// 為了讓圓點(diǎn)飽滿一些姨涡,把角度0.9°增加0.1°到1°
float dotRadius = mSin_1 * outerRadius / (1 + mSin_1);
// 畫進(jìn)度
mPaint.setColor(dotColor);
mPaint.setStyle(Paint.Style.FILL);
int count = 0;
// 1.1 當(dāng)前進(jìn)度
while (count++ < percent) {
//mCanvas.drawCircle(mCenterX, mCenterY - outerRadius + dotRadius, dotRadius, mPaint);
mCanvas.drawRect(mCenterX-size/2,getHeight()-size,mCenterX+size/2,getHeight(),mPaint);
//? ? mCanvas.drawRect(mCenterX-50/2,0,mCenterX+50/2,50,mPaint);
mCanvas.rotate(30f, mCenterX, mCenterY); //3.6f
Log.e(" count ",count+"");
Log.e(" percent ",percent+"");
}
// 1.2 未完成進(jìn)度
mPaint.setColor(dotBgColor);
count--;
while (count++ < 12) {
mCanvas.drawCircle(mCenterX, mCenterY - outerRadius + dotRadius, dotRadius, mPaint);
// mCanvas.drawRect(mCenterX-50/2,0,mCenterX+50/2,50,mPaint);
mCanvas.rotate(30f, mCenterX, mCenterY);
}
}
private int dp2px(int dp) {
float density = getContext().getResources().getDisplayMetrics().density;
return (int) (dp * density + .5f);
}
/**
* Setter and Getter
*/
public synchronized int getProgressMax() {
return progressMax;
}
public synchronized void setProgressMax(int progressMax) {
if (progressMax < 0) {
throw new IllegalArgumentException("progressMax mustn't smaller than 0");
}
this.progressMax = progressMax;
}
public synchronized int getProgress() {
return progress;
}
/**
* 設(shè)置進(jìn)度
* 同步,允許多線程訪問
* @param progress 進(jìn)度
*/
public synchronized void setProgress(int progress) {
if (progress < 0 || progress > progressMax) {
throw new IllegalArgumentException(String.format(getResources().getString(R.string.CircleDotProgressBar_progress_out_of_range), progressMax));
}
this.progress = progress;
percent = progress * 12 / progressMax;
postInvalidate(); // 可以直接在子線程中調(diào)用吧慢,而invalidate()必須在主線程(UI線程)中調(diào)用
}