Android自定义控件实现带数值和动画的圆形进度条

作者:王世晖 时间:2021-09-09 22:02:46 

本文实例实现一个如下图所示的Android自定义控件,可以直观地展示某个球队在某个赛季的积分数和胜场、负场、平局数

Android自定义控件实现带数值和动画的圆形进度条

首先对画布进行区域划分,整个控件分上下两部分

上边是个大的圆环,圆环中间两行文字,没什么难度,选好圆心坐标和半径后直接绘制即可,绘制文字也是如此。

下部分是三个小的圆弧进度条,弧的末端绘制一个小的实心圆

首先选好坐标和半径,然后先绘制三个圆环作为弧形进度条的背景

之后从12点钟开始绘制进度弧,知道了圆环的圆心和半径,也知道了弧对应于12点钟和圆环圆心的偏移角度

通过三角函数可以计算出进度弧终点坐标,以进度弧终点坐标为圆心绘制一个小的实心圆即可

动画效果通过Handler的postDelayed方法触发重绘即可实现

在项目中的效果如图所示:

Android自定义控件实现带数值和动画的圆形进度条

测试代码如下:


final Random random=new Random();
final ScoreBoardView myView=(ScoreBoardView)findViewById(R.id.custom_view);
myView.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
 myView.setColor(Color.BLUE);
 myView.setScore(random.nextInt(28));
 myView.setWinDrawLose(random.nextInt(12),random.nextInt(15),random.nextInt(26));
}
});

完整代码如下:


public class ScoreBoardView extends View {
private Context context;
/*弧的颜色*/
private int mColor;
/*积分数,胜场数,平局数,负场数*/
private int mScore, mWinNumber, mDrawNumber, mLoseNumber;
/*传入数字的最大值*/
private final int FULL_SCORE = 30;
/*动画插值器*/
DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
/*动画持续时间(刷新次数)*/
private int mDuration = 10;
/*动画刷新过程中的当前值*/
private int mCurrentTime = 0;
private TypedValue typedValue;
private TypedValue typedValue1;
private Handler mHandler = new Handler();
private Runnable mAnimation = new Runnable() {
 @Override
 public void run() {
  if (mCurrentTime < mDuration) {
   mCurrentTime++;
   /*导致重绘调用onDraw,onDraw最后再次postDelay执行此动画,直到达到指定的次数*/
   ScoreBoardView.this.invalidate();
  }
 }
};
/*绘制图形*/
private Paint paint = new Paint();
/*绘制文字*/
private Paint paintText = new Paint();

public ScoreBoardView(Context context) {
 super(context);
 this.context=context;
 init();
}

public ScoreBoardView(Context context, AttributeSet attrs) {
 super(context, attrs);
 this.context=context;
 init();
}

public ScoreBoardView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 this.context=context;
 init();
}

private void init() {
 /*数据初始化,默认属性*/
 mColor = Color.rgb(95, 112, 72);
 mScore = 0;
 mWinNumber = 0;
 mDrawNumber = 0;
 mLoseNumber = 0;
 typedValue=new TypedValue();
 typedValue1=new TypedValue();
 context.getTheme().resolveAttribute(R.attr.maintextclor, typedValue, true);
 context.getTheme().resolveAttribute(R.attr.maintextclor_reverse,typedValue1,true);
}

@Override
protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 /*获取控件总的宽高*/
 float totalWidth = getWidth();
 float totalHeight = getHeight();
 /*
 * DecelerateInterpolator:动画从开始到结束,变化率是一个减速的过程。
 * AccelerateInterpolator:动画从开始到结束,变化率是一个加速的过程。
 * CycleInterpolator:动画从开始到结束,变化率是循环给定次数的正弦曲线
 * AccelerateDecelerateInterpolator:动画从开始到结束,变化率是先加速后减速的过程。
 * LinearInterpolator:动画从开始到结束,变化率是线性变化。
 * */
 /*计算当前时刻动画进度值*/
 float AnimCurrentValue =mDecelerateInterpolator.getInterpolation(1.0f * mCurrentTime / mDuration);

/*图形画笔设置*/
 paint.setAntiAlias(true);
 paint.setStyle(Paint.Style.STROKE);

/*积分数,上边的大圆*/
 paint.setStrokeWidth(4);
 paint.setColor(mColor);
 /*积分大圆的中心坐标和半径*/
 float score_radius = totalHeight * 1 / 5, score_circle_x = totalWidth / 2, score_circle_y = totalHeight / 3;
 /*绘制圆弧*/
 canvas.drawCircle(score_circle_x, score_circle_y, score_radius, paint);
 /*文字画笔基本设置*/
 paintText.setAntiAlias(true);
 paintText.setStyle(Paint.Style.STROKE);
 /*文字从中间开始绘制*/
 /*Paint.Align.CENTER:The text is drawn centered horizontally on the x,y origin*/
 paintText.setTextAlign(Paint.Align.CENTER);
 /*文字画笔大小和颜色设置*/
 paintText.setTextSize(score_radius * 3 / 4);
 paintText.setColor(getResources().getColor(typedValue.resourceId));
 /*圆心位置绘制积分数值*/
 canvas.drawText("" + mScore, score_circle_x, score_circle_y, paintText);
 /*缩小字体绘制文本信息*/
 paintText.setTextSize(score_radius * 1 / 4);
 paintText.setAlpha(80);
 /*圆心下边绘制文本*/
 canvas.drawText("积分", score_circle_x, score_circle_y + score_radius / 2, paintText);

/*设置画笔,画下边的三个小圆*/
 paint.setStrokeWidth(4);
 paint.setAlpha(255);
 /*下边三个小圆的半径*/
 float small_radius = totalHeight / 10;
 /*三个小圆的圆心的x坐标*/
 float[] circleXs = new float[]{totalWidth / 2 - score_radius * 3 / 2,
         totalWidth / 2,
         totalWidth / 2 + score_radius * 3 / 2};
 /*三个小圆的圆心的y坐标*/
 float circleY = totalHeight * 3 / 4;
 /*计算三个小圆弧扫过的角度*/
 float[] theta_values = new float[]{360 * mWinNumber / FULL_SCORE* AnimCurrentValue,
          360 * mDrawNumber / FULL_SCORE* AnimCurrentValue,
          360 * mLoseNumber / FULL_SCORE* AnimCurrentValue};
 /*设置画笔颜色,绘制外围圆环*/
 paint.setColor(getResources().getColor(typedValue1.resourceId));
 /*分别绘制三个外围圆环*/
 canvas.drawCircle(circleXs[0], circleY, small_radius, paint);//画WIN背景圆
 canvas.drawCircle(circleXs[1], circleY, small_radius, paint);//画DRAW背景圆
 canvas.drawCircle(circleXs[2], circleY, small_radius, paint);//画LOSE背景圆
 /*更改画笔颜色,绘制圆弧进度条*/
 paint.setColor(mColor);
 /*drawArc的起始角度是3点钟方向,因此要从12点钟方向开始绘制,起始角度为270度*/
 canvas.drawArc(new RectF(circleXs[0] - small_radius,
        circleY - small_radius,
        circleXs[0] + small_radius,
        circleY + small_radius),
     270, theta_values[0], false, paint);//画WIN圆形进度条
 canvas.drawArc(new RectF(circleXs[1] - small_radius,
        circleY - small_radius,
        circleXs[1] + small_radius,
        circleY + small_radius),
     270, theta_values[1], false, paint);//画DRAW圆形进度条
 canvas.drawArc(new RectF(circleXs[2] - small_radius,
        circleY - small_radius,
        circleXs[2] + small_radius,
        circleY + small_radius),
     270, theta_values[2], false, paint);//画LOSE圆形进度条
 /*绘制圆弧结束处的小圆点,实心圆*/
 paint.setStyle(Paint.Style.FILL);
 /*已知半径、圆心位置、便宜角度,根据三角函数很方便计算出小实心圆圆心坐标*/
 canvas.drawCircle(circleXs[0] + small_radius * (float) Math.sin(theta_values[0] * Math.PI / 180),
   circleY - small_radius * (float) Math.cos(theta_values[0] * Math.PI / 180), 6, paint);//画WIN末尾小圆点
 canvas.drawCircle(circleXs[1] + small_radius * (float) Math.sin(theta_values[1] * Math.PI / 180),
   circleY - small_radius * (float) Math.cos(theta_values[1] * Math.PI / 180), 6, paint);//画DRAW末尾小圆点
 canvas.drawCircle(circleXs[2] + small_radius * (float) Math.sin(theta_values[2] * Math.PI / 180),
   circleY - small_radius * (float) Math.cos(theta_values[2] * Math.PI / 180), 6, paint);//画LOSE末尾小圆点

/*绘制文字*/
 paintText.setColor(getResources().getColor(typedValue.resourceId));
 paintText.setTextSize(small_radius * 2 / 3);
 /*测量文字大小,确定绘制文字时垂直方向的位置*/
 Paint.FontMetrics fontMetrics = paint.getFontMetrics();
 float textBaseLineOffset = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
 /*在三个小圆的正中心位置绘制相应的数字*/
 canvas.drawText("" + (int)(mWinNumber * AnimCurrentValue), circleXs[0], circleY + textBaseLineOffset, paintText);
 canvas.drawText("" + (int)(mDrawNumber * AnimCurrentValue), circleXs[1], circleY + textBaseLineOffset, paintText);
 canvas.drawText("" + (int)(mLoseNumber * AnimCurrentValue), circleXs[2], circleY + textBaseLineOffset, paintText);
 /*调整字体大小,绘制文本信息*/
 paintText.setTextSize(small_radius * 4 / 9);
 canvas.drawText("胜场", circleXs[0], circleY - small_radius*4/3, paintText);
 canvas.drawText("平局", circleXs[1], circleY - small_radius*4/3, paintText);
 canvas.drawText("负场", circleXs[2], circleY - small_radius*4/3, paintText);
 /*20ms刷新一次数据*/
 mHandler.postDelayed(mAnimation, 20);//启动动画
}

public void setColor(int mColor) {
 this.mColor = mColor;
 invalidate();
}

public void setScore(int score) {
 this.mScore = score;
 invalidate();
}

public void setWinDrawLose(int win,int draw,int lose) {
 this.mWinNumber = win;
 this.mDrawNumber = draw;
 this.mLoseNumber = lose;
 mCurrentTime =0;
 invalidate();
}
}

来源:https://blog.csdn.net/wangshihui512/article/details/51428342

标签:Android,进度条
0
投稿

猜你喜欢

  • struts中动态方法调用使用通配符

    2023-02-01 02:52:43
  • 浅谈Java方法调用的优先级问题

    2023-07-01 13:40:08
  • JavaSE的类和对象你真的了解吗

    2021-09-17 21:14:59
  • Android View与Compose互相调用实例探究

    2021-06-11 09:07:29
  • Java使用雪花id生成算法详解

    2023-11-18 21:58:49
  • Java Resource路径整理总结

    2021-11-24 06:52:31
  • 基于SPRINGBOOT配置文件占位符过程解析

    2021-06-27 04:25:12
  • spring boot补习系列之几种scope详解

    2022-06-10 13:39:13
  • Java中使用Lambda表达式和函数编程示例

    2022-05-06 03:30:40
  • Spring自动配置之condition条件判断上篇

    2022-07-01 17:34:51
  • 深入浅析Java反射机制

    2023-11-25 07:02:03
  • Spring Mvc下实现以文件流方式下载文件的方法示例

    2023-11-12 10:14:22
  • java 排序算法之快速排序

    2022-07-23 17:39:03
  • c# 深拷贝与浅拷贝的区别分析及实例

    2023-06-29 05:00:06
  • Java基础之堆内存溢出的解决

    2023-11-10 20:06:30
  • WinForm中Application.Idle方法详解

    2022-09-05 01:53:06
  • 详解Lombok安装及Spring Boot集成Lombok

    2023-11-28 23:39:55
  • 解决idea删除模块后重新创建显示该模块已经被注册的问题

    2023-04-24 18:06:12
  • 基于Java检查IPv6地址的合法性

    2022-08-05 12:17:17
  • Java获取json数组对象的实例讲解

    2023-08-24 14:55:28
  • asp之家 软件编程 m.aspxhome.com