Android 自定义view之圆盘进度条

电玩女神 2022-06-01 22:28 389阅读 0赞

很久没有用到自定义View了,手有点生疏了,这不同事刚扔给一个活,按照UI的要求,画一个进度条,带动画效果的。需求是这样的:这里写图片描述
嗯,实现后效果如下:

这里写图片描述

嗯,算是基本满足需求吧。
本文包含的知识点
1、自定义view的绘制
2、属性动画
3、图像的合成模式 PorterDuff.Mode

嗯,废话不多说,show me the code
1)WordView.java

  1. import android.animation.ValueAnimator;
  2. import android.content.Context;
  3. import android.content.res.TypedArray;
  4. import android.graphics.Canvas;
  5. import android.graphics.Color;
  6. import android.graphics.Paint;
  7. import android.graphics.PorterDuff;
  8. import android.graphics.PorterDuffXfermode;
  9. import android.graphics.RectF;
  10. import android.graphics.Xfermode;
  11. import android.support.annotation.Nullable;
  12. import android.util.AttributeSet;
  13. import android.util.Log;
  14. import android.view.View;
  15. /**
  16. * Author: gongwq on 2017/12/20 002011.
  17. * Email: gwq_0111@163.com
  18. * Descriptions:
  19. */
  20. public class WordView extends View {
  21. private Paint linePaint, circlePaint, circlePaintBg;
  22. private Paint textPaint1, textPaint2, textPaint3, textPaint4;
  23. private int screenWidth, screenHeight;
  24. private int mCenter, mRadius;
  25. private RectF mRectF, mRectFBg1, mRectFBg2;
  26. private int defaultValue;
  27. private float rate = 0f;
  28. private String hasStudyText = "";
  29. private String notStudyText = "";
  30. private int startAng = 0;
  31. private int defaultStartAng = 120;
  32. private long animDuration = 2000L;
  33. private int progressColor, wordTitleColor, wordViewBackground;
  34. private Xfermode xfermode;
  35. public WordView(Context context, @Nullable AttributeSet attrs) {
  36. super(context, attrs);
  37. TypedArray ta = context.obtainStyledAttributes(
  38. attrs, R.styleable.WordView);
  39. progressColor = ta.getColor(R.styleable.WordView_progressColor, 0xFFFFFFFF);
  40. wordViewBackground = ta.getColor(R.styleable.WordView_wordViewBackground, 0xFFFF0000);
  41. wordTitleColor = ta.getColor(R.styleable.WordView_wordTitleColor, 0xFFFF00FF);
  42. ta.recycle();
  43. initPaint(context);
  44. }
  45. private void initPaint(Context context) {
  46. screenWidth = MeasureUtil.getScreenWidth(context);
  47. screenHeight = MeasureUtil.getScreenHeight(context);
  48. linePaint = new Paint();
  49. linePaint.setColor(Color.WHITE);
  50. linePaint.setStyle(Paint.Style.FILL);
  51. linePaint.setAntiAlias(true);
  52. linePaint.setStrokeWidth(6.0f);
  53. circlePaint = new Paint();
  54. circlePaint.setColor(progressColor);
  55. circlePaint.setAntiAlias(true);
  56. circlePaint.setStyle(Paint.Style.STROKE);
  57. circlePaint.setStrokeCap(Paint.Cap.ROUND);
  58. circlePaint.setStrokeWidth(6.0f);
  59. circlePaintBg = new Paint();
  60. circlePaintBg.setColor(wordViewBackground);
  61. circlePaintBg.setAntiAlias(true);
  62. circlePaintBg.setStyle(Paint.Style.FILL);
  63. textPaint1 = new Paint();//已学习生词
  64. textPaint1.setColor(wordTitleColor);
  65. textPaint1.setTextAlign(Paint.Align.CENTER);
  66. textPaint1.setAntiAlias(true);
  67. textPaint1.setTextSize(25);
  68. textPaint2 = new Paint();//xx个
  69. textPaint2.setColor(Color.WHITE);
  70. textPaint2.setTextAlign(Paint.Align.CENTER);
  71. textPaint2.setAntiAlias(true);
  72. textPaint2.setTextSize(70);
  73. textPaint4 = new Paint();//个
  74. textPaint4.setColor(Color.WHITE);
  75. textPaint4.setTextAlign(Paint.Align.CENTER);
  76. textPaint4.setAntiAlias(true);
  77. textPaint4.setTextSize(25);
  78. textPaint3 = new Paint();//未学习生词x个
  79. textPaint3.setColor(wordTitleColor);
  80. textPaint3.setTextAlign(Paint.Align.CENTER);
  81. textPaint3.setAntiAlias(true);
  82. textPaint3.setTextSize(25);
  83. mCenter = screenWidth / 2;
  84. mRadius = screenWidth / 4;
  85. mRectF = new RectF(10, 10, 2 * mRadius - 10, 2 * mRadius - 10);//外切圆弧
  86. mRectFBg1 = new RectF(20, 20, 2 * mRadius - 20, 2 * mRadius - 20);//中间的背景
  87. mRectFBg2 = new RectF(60, 2 * mRadius - 60, 2 * mRadius - 60, 2 * mRadius + 60);//通过这个去调下面“缺”的弧度
  88. xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);//DST_OUT在不相交的地方绘制目标图像,相交处根据源图像alpha进行过滤,完全不透明处则完全过滤,完全透明则不过滤
  89. }
  90. @Override
  91. protected void onDraw(Canvas canvas) {
  92. super.onDraw(canvas);
  93. canvas.drawArc(mRectF, startAng, (360 - (startAng - 90) * 2) * rate, false, circlePaint);
  94. //将绘制操作保存到新的图层,将图像合成的处理放到离屏缓存中进行
  95. circlePaintBg.setColor(wordViewBackground);
  96. int saveCount = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
  97. canvas.drawArc(mRectFBg1, 0, 360, true, circlePaintBg);//绘制目标图
  98. circlePaintBg.setXfermode(xfermode);//设置混合模式
  99. circlePaintBg.setColor(Color.parseColor("#FF00FF00"));//这里的颜色只需前面的透明值为FF即完全不透明即可
  100. canvas.drawArc(mRectFBg2, 0, 360, true, circlePaintBg);//绘制源图
  101. circlePaintBg.setXfermode(null);//清除混合模式
  102. canvas.restoreToCount(saveCount);
  103. canvas.drawText("已学习生词", mRadius, mRectF.top + 100, textPaint1);
  104. float width = textPaint2.measureText(hasStudyText);
  105. float width2 = textPaint4.measureText("个");
  106. // float total = mRectFBg1.right - mRectFBg1.left;
  107. // Log.e("文字宽度---->", width + " " + width2 + " " + total);
  108. // canvas.drawText(hasStudyText, mRadius + 30, mRadius, textPaint2);
  109. float center1 = mRadius - (width + width2) / 2 + width / 2;
  110. float center2 = mRadius - (width + width2) / 2 + width + width2 / 2;
  111. canvas.drawText(hasStudyText, center1, mRadius + 20, textPaint2);
  112. canvas.drawText(notStudyText, mRadius, mRectF.bottom - 100, textPaint3);
  113. if (startAng != 0) {
  114. canvas.drawText("个", center2, mRadius + 20, textPaint4);
  115. canvas.rotate(180 + (startAng - 90) + (360 - (startAng - 90) * 2) * rate, mRadius, mRadius);
  116. if (Integer.parseInt(hasStudyText) > 0) {
  117. canvas.drawLine(mRadius, mRectF.top, mRadius, mRectF.top + 20, linePaint);
  118. }
  119. }
  120. }
  121. /**
  122. * 设置学习单词数
  123. *
  124. * @param hasStudyNum 已学习单词数
  125. * @param notStudyNum 未学习单词数
  126. */
  127. public void setWordsNum(final int hasStudyNum, final int notStudyNum) {
  128. rate = (float) hasStudyNum / (float) (hasStudyNum + notStudyNum);
  129. final float rate2 = rate;
  130. startAng = getStartAng();//startAng必须为大于等于90,小于180
  131. ValueAnimator anim = ValueAnimator.ofFloat(1, 100);
  132. anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  133. @Override
  134. public void onAnimationUpdate(ValueAnimator animation) {
  135. rate = (float) animation.getAnimatedValue() * rate2 / 100f;
  136. hasStudyText = (int) ((float) animation.getAnimatedValue() * hasStudyNum / 100) + "";
  137. notStudyText = "未学习生词" + (int) ((float) animation.getAnimatedValue() * notStudyNum / 100) + "个";
  138. postInvalidate();
  139. }
  140. });
  141. // hasStudyText = hasStudyNum + "";
  142. // notStudyText = "未学习生词" + notStudyNum+"个";
  143. anim.setDuration(getAnimationDuration());
  144. anim.start();
  145. }
  146. /**
  147. * 设置圆弧的起始角度值 <br>注 1.值必须是[90,180] 2.必须在setWordNum()方法之前调用
  148. * @param ang
  149. */
  150. public void setStartAng(int ang) {
  151. this.startAng = ang;
  152. }
  153. public int getStartAng() {
  154. if (startAng == 0) {
  155. return defaultStartAng;
  156. }
  157. return startAng;
  158. }
  159. /**
  160. * 设置动画时间 ,注意 需要在setWordsNum前调用才会生效
  161. *
  162. * @param time
  163. */
  164. public void setAnimationDuration(long time) {
  165. this.animDuration = time;
  166. }
  167. public long getAnimationDuration(){
  168. return animDuration;
  169. }
  170. @Override
  171. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  172. setMeasuredDimension(measureWidth(widthMeasureSpec),
  173. measureHeight(heightMeasureSpec));
  174. }
  175. private int measureWidth(int widthMeasureSpec) {
  176. int mode = MeasureSpec.getMode(widthMeasureSpec);
  177. int size = MeasureSpec.getSize(widthMeasureSpec);
  178. // 默认宽高;
  179. defaultValue = screenWidth;
  180. switch (mode) {
  181. case MeasureSpec.AT_MOST:
  182. // 最大值模式 当控件的layout_Width或layout_height属性指定为wrap_content时
  183. // Log.e("cmos---->", "size " + size + " screenWidth " + screenWidth);
  184. // size = Math.min(defaultValue, size);
  185. size = screenWidth / 2;
  186. defaultValue = size;
  187. break;
  188. case MeasureSpec.EXACTLY:
  189. // 精确值模式
  190. // 当控件的android:layout_width=”100dp”或android:layout_height=”match_parent”时
  191. break;
  192. default:
  193. size = defaultValue;
  194. break;
  195. }
  196. return size;
  197. }
  198. private int measureHeight(int heightMeasureSpec) {
  199. int mode = MeasureSpec.getMode(heightMeasureSpec);
  200. int size = MeasureSpec.getSize(heightMeasureSpec);
  201. switch (mode) {
  202. case MeasureSpec.AT_MOST:
  203. // 最大值模式 当控件的layout_Width或layout_height属性指定为wrap_content时
  204. Log.e("cmos---->", "size " + size + " screenHeight " + screenHeight);
  205. // size = Math.min(screenHeight / 2, size);
  206. size = defaultValue;
  207. break;
  208. case MeasureSpec.EXACTLY:
  209. // 精确值模式
  210. // 当控件的android:layout_width=”100dp”或android:layout_height=”match_parent”时
  211. break;
  212. default:
  213. size = defaultValue;
  214. break;
  215. }
  216. return size;
  217. }
  218. }

下面是他的一些配置
attrs.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="WordView">
  4. <attr name="progressColor" format="color" />
  5. <attr name="wordTitleColor" format="color" />
  6. <attr name="wordViewBackground" format="color" />
  7. </declare-styleable>
  8. </resources>

布局文件
activity_main.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:background="@mipmap/details_top_bg"
  7. android:gravity="center_horizontal"
  8. android:orientation="vertical">
  9. <EditText
  10. android:id="@+id/et1"
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. android:hint="已学习生词数"
  14. android:inputType="number" />
  15. <EditText
  16. android:id="@+id/et2"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:hint="未学习生词数"
  20. android:inputType="number" />
  21. <Button
  22. android:id="@+id/bt"
  23. android:layout_width="wrap_content"
  24. android:layout_height="wrap_content"
  25. android:text="绘制" />
  26. <com.example.myapplication.WordView
  27. android:id="@+id/customView"
  28. android:layout_width="wrap_content"
  29. android:layout_height="wrap_content"
  30. app:progressColor="#ffffff"
  31. app:wordTitleColor="#ffffff"
  32. app:wordViewBackground="#61000000" />
  33. </LinearLayout>

屏幕测量工具
MeasureUtil.java

  1. public class MeasureUtil {
  2. public static int getScreenWidth(Context mContext) {
  3. int width = mContext.getResources().getDisplayMetrics().widthPixels;
  4. return width;
  5. }
  6. public static int getScreenHeight(Context mContext) {
  7. int height = mContext.getResources().getDisplayMetrics().heightPixels;
  8. return height;
  9. }
  10. }

使用
MainActivity.java

  1. ......
  2. @Override
  3. public void onClick(View v) {
  4. switch (v.getId()) {
  5. case R.id.bt:
  6. if (!TextUtils.isEmpty(et1.getText().toString()) && !TextUtils.isEmpty(et2.getText().toString())) {
  7. int num1 = Integer.parseInt(et1.getText().toString());
  8. int num2 = Integer.parseInt(et2.getText().toString());
  9. wordView.setAnimationDuration(4000);
  10. wordView.setStartAng(130);
  11. wordView.setWordsNum(num1, num2);
  12. } else {
  13. Toast.makeText(this, "数值不能为空", Toast.LENGTH_SHORT).show();
  14. }
  15. break;
  16. }
  17. }
  18. ......

代码里注释已经相对较清晰了,就不做解释了,有不懂的可以留言。
整个view的重点就在onDraw()方法里,怎么去放置文字,中间的“xxx个”怎么随着数字的长度变化而始终居中,这主要与initPaint()画笔方法有关,其中textPaint.setTextAlign(Paint.Align.CENTER);是重点,它表示画的文字,你后面给定他一个绘制的中心点,然后它的文字会自动居中。第二个要注意的地方是,中间的背景,我是画的,开始准备用UI给的背景的,但是发现不好适配,所以就自己画了,这里主要用到的是图像合成模式PorterDuff.Mode,图像的合成模式的枚举类一共有16种,通过这16种模式,我们可以自己根据给定的2个图片,合成我们想要的结果。这也给我们一个启示:当需求中的图片可以看成是多个图片组合成的结果的画,不妨可以试试 图像的合成模式PorterDuff.Mode。关于PorterDuff.Mode,可以查看我的下一篇文章。 Android图像合成模式之PorterDuff.Mode

发表评论

表情:
评论列表 (有 0 条评论,389人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Android 定义圆形进度

    在Android开发中,对于进度条想必大家不会陌生。例如,应用在执行一个耗时操作时,会通过展示一个进度条来显示“加载中...”的动画作为友好页面以提高用户体验。对于这样的进度条