效果图如下:
一。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); 方法进行旋转,总共一圈12个正方形,360/12=30,每一圈12个正方形每一次旋转30度
绘制正方形代码如下:
int count = 0;
Log.e("mpercent",percent + "");
// 1.1 当前进度
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 未完成进度
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);
}
这里每一次重绘需要旋转12次,按进度的变化,不断绘制红色矩形,剩余的用白色替代,不然会出现角度错乱的现象.算出每一次旋转的百分比:
percent = progress * 12 / progressMax;
2.绘制中间图形,这个比较容易了,直接调用 mCanvas.drawBitmap方法,先算出中心点的坐标,获取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.绘制移动的矩形,这个不断的去改变矩形的左上和右下的坐标,每一次重绘时记得清空画布,不然原来图形会存在,按照百分比去不断增加剩余的高度,具体代码如下
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
{
/**
* 当前进度
*/
private int progress;
/**
* 当前百分比
*/
private int percent;
/**
* 最大进度
*/
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);
// 获取自定义属性值或默认值
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();
// 其他准备工作
mSin_1 = (float) Math.sin(Math.toRadians(1)); // 求sin(1°)。角度需转换成弧度
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);
// 绘制外围圆点进度
// 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:
// 计算圆点半径
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;
// 为了让圆点饱满一些,把角度0.9°增加0.1°到1°
float dotRadius = mSin_1 * outerRadius / (1 + mSin_1);
// 画进度
mPaint.setColor(dotColor);
mPaint.setStyle(Paint.Style.FILL);
int count = 0;
Log.e("mpercent",percent + "");
// 1.1 当前进度
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 未完成进度
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);
}
/**
* 绘制圆点进度
* @param canvas 画布
*/
private void drawCircleDot(Canvas canvas) {
// 先清除上次绘制的
mPaint.setXfermode(mClearCanvasXfermode);
mCanvas.drawPaint(mPaint);
mPaint.setXfermode(null);
// 计算圆点半径
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;
// 为了让圆点饱满一些,把角度0.9°增加0.1°到1°
float dotRadius = mSin_1 * outerRadius / (1 + mSin_1);
// 画进度
mPaint.setColor(dotColor);
mPaint.setStyle(Paint.Style.FILL);
int count = 0;
// 1.1 当前进度
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 未完成进度
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;
}
/**
* 设置进度
* 同步,允许多线程访问
* @param progress 进度
*/
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(); // 可以直接在子线程中调用,而invalidate()必须在主线程(UI线程)中调用
}