【Android 应用开发】 自定义 圆形进度条 组件

举报
韩曙亮 发表于 2022/01/11 01:37:57 2022/01/11
【摘要】 转载著名出处 : http://blog.csdn.net/shulianghan/article/details/40351487 代码下载 :  -- CSDN 下载地址 : http://download.csdn.net/detail/han1202012/8069497 ; -- GitHub ...

转载著名出处 : http://blog.csdn.net/shulianghan/article/details/40351487


代码下载

-- CSDN 下载地址http://download.csdn.net/detail/han1202012/8069497 ;

-- GitHub 地址https://github.com/han1202012/CircleProcess.git ;

-- 工程示例



一. 相关知识点解析



1. 自定义 View 组件构造方法


构造方法 : 自定义的 View 组件, 一般需要实现 三个构造方法, 分别有 一个, 两个, 三个参数;

-- 一个参数public CircleProcess(Context context);

-- 两个参数public CircleProcess(Context context, AttributeSet attrs);

-- 三个参数public CircleProcess(Context context, AttributeSet attrs, int defStyle);


构造方法注意点

-- 调用上级方法 : 每个构造方法中必须调用 super() 方法, 方法中的参数与该构造方法参数一样;

-- 常用构造方法 : 一般在2参数构造方法中实现逻辑;


构造方法示例


  
  1. /** 画笔 */
  2. private Paint mPaint;
  3. /** 上下文对象 */
  4. private Context mContext;
  5. /** 进度条的值 */
  6. private int mProcessValue;
  7. public CircleProcess(Context context, AttributeSet attrs) {
  8. super(context, attrs);
  9. // 初始化成员变量 Context
  10. mContext = context;
  11. // 创建画笔, 并设置画笔属性
  12. mPaint = new Paint();
  13. // 消除绘制时产生的锯齿
  14. mPaint.setAntiAlias(true);
  15. // 绘制空心圆形需要设置该样式
  16. mPaint.setStyle(Style.STROKE);
  17. }
  18. /**
  19. * 自定义布局实现的 只有 Context 参数的构造方法
  20. * @param context
  21. */
  22. public CircleProcess(Context context) {
  23. super(context);
  24. }
  25. /**
  26. * 自定义布局实现的 三个参数的构造方法
  27. * @param context
  28. * @param attrs
  29. * @param defStyle
  30. */
  31. public CircleProcess(Context context, AttributeSet attrs, int defStyle) {
  32. super(context, attrs, defStyle);
  33. }


2. dip 和 px 单位转换



(1) dip 转 px


公式

-- 基本公式px / dip = dpi / 160;

-- 计算公式 : px = dpi / 160 * dip;


一些概念解析

-- dpi 概念 : dpi (dot per inch), 每英寸像素数 归一化的值 120 160 240 320 480;

-- 区分 dpi 和 density : dpi 是归一化的值, density 是实际的值, 可能不是整数;


代码示例


  
  1. /**
  2. * 将手机的 设备独立像素 转为 像素值
  3. *
  4. * 公式 : px / dip = dpi / 160
  5. * px = dip * dpi / 160;
  6. * @param context
  7. * 上下文对象
  8. * @param dpValue
  9. * 设备独立像素值
  10. * @return
  11. * 转化后的 像素值
  12. */
  13. public static int dip2px(Context context, float dpValue) {
  14. final float scale = context.getResources().getDisplayMetrics().density;
  15. return (int) (dpValue * scale + 0.5f);
  16. }


(2) px 转 dip


公式

-- 基本公式 : px / dip = dpi / 160;

-- 计算公式 : dip = 160 / dpi * px;


代码


  
  1. /**
  2. * 将手机的 像素值 转为 设备独立像素
  3. * 公式 : px/dip = dpi/160
  4. * dip = px * 160 / dpi
  5. * dpi (dot per inch) : 每英寸像素数 归一化的值 120 160 240 320 480;
  6. * density : 每英寸的像素数, 精准的像素数, 可以用来计算准确的值
  7. * 从 DisplayMetics 中获取的
  8. * @param context
  9. * 上下文对象
  10. * @param pxValue
  11. * 像素值
  12. * @return
  13. * 转化后的 设备独立像素值
  14. */
  15. public static int px2dip(Context context, float pxValue) {
  16. final float scale = context.getResources().getDisplayMetrics().density;
  17. return (int) (pxValue / scale + 0.5f);
  18. }



3. 关于 组件 宽 和 高 计算



(1) MesureSpec 简介


MeasureSpec 组成 : 每个 MeasureSpec 都代表一个 int 类型数值, 共 32 位, 前两位是模式位, 后 30 位 是数值位;

-- 模式 : int 类型的前 2 位, 共有三种模式, 通过 MeasureSpec.getMode(int) 方法获取, 下面会详细介绍模式;

-- 大小 : int 类型的后 30 位, 通过 MeasureSpec.getSize(int) 方法获取大小;


MeasureSpec 模式简介注意下面的数字是二进制的

-- 00 : MeasureSpec.UNSPECIFIED, 未指定模式;
-- 01 : MeasureSpec.EXACTLY, 精准模式;
-- 11 : MeasureSpec.AT_MOST, 最大模式;


MeasureSpec 常用方法介绍

-- MeasureSpec.getMode(int) : 获取模式;
-- MeasureSpec.getSize(int) : 获取大小;
-- MeasureSpec.makeMeasureSpec(int size, int mode) : 创建一个 MeasureSpec;
-- MeasureSpec.toString(int) : 模式 + 大小 字符串;



(2) 通过 MeasureSpec 计算组件大小


计算方法

-- 精准模式 : 该模式下 长度的大小 就是 从 MeasureSpec 中获取的 size 大小;

-- 最大模式 : 获取 默认大小 和 size 中的较小的那个;

-- 未定义模式 : 默认大小;


通用计算方法代码


  
  1. /**
  2. * 获取组件宽度
  3. *
  4. * MeasureSpec : 该 int 类型有 32 位, 前两位是状态位, 后面 30 位是大小值;
  5. * 常用方法 :
  6. * -- MeasureSpec.getMode(int) : 获取模式
  7. * -- MeasureSpec.getSize(int) : 获取大小
  8. * -- MeasureSpec.makeMeasureSpec(int size, int mode) : 创建一个 MeasureSpec;
  9. * -- MeasureSpec.toString(int) : 模式 + 大小 字符串
  10. *
  11. * 模式介绍 : 注意下面的数字是二进制的
  12. * -- 00 : MeasureSpec.UNSPECIFIED, 未指定模式;
  13. * -- 01 : MeasureSpec.EXACTLY, 精准模式;
  14. * -- 11 : MeasureSpec.AT_MOST, 最大模式;
  15. *
  16. * 注意 : 这个 MeasureSpec 模式是在 onMeasure 方法中自动生成的, 一般不用去创建这个对象
  17. *
  18. * @param widthMeasureSpec
  19. * MeasureSpec 数值
  20. * @return
  21. * 组件的宽度
  22. */
  23. private int measure(int measureSpec) {
  24. //返回的结果, 即组件宽度
  25. int result = 0;
  26. //获取组件的宽度模式
  27. int mode = MeasureSpec.getMode(measureSpec);
  28. //获取组件的宽度大小 单位px
  29. int size = MeasureSpec.getSize(measureSpec);
  30. if(mode == MeasureSpec.EXACTLY){//精准模式
  31. result = size;
  32. }else{//未定义模式 或者 最大模式
  33. //注意 200 是默认大小, 在 warp_content 时使用这个值, 如果组件中定义了大小, 就不使用该值
  34. result = dip2px(mContext, 200);
  35. if(mode == MeasureSpec.AT_MOST){//最大模式
  36. //最大模式下获取一个稍小的值
  37. result = Math.min(result, size);
  38. }
  39. }
  40. return result;
  41. }


(3) 设置 组件大小方法


setMeasuredDimension() 方法 : 该方法决定 View 组件的大小;

-- 使用场所 : 在 onMeasure() 方法中调用该方法, 就设置了组件的宽 和 高, 然后在其它位置调用 getWidth() 和 getHeight() 方法时, 获取的就是 该方法设置的值;

-- 代码示例


  
  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  4. /*
  5. * setMeasuredDimension 方法 : 该方法决定当前的 View 的大小
  6. * 根据 View 在布局中的显示, 动态获取 View 的宽高
  7. *
  8. * 当布局组件 warp_content 时 :
  9. * 从 MeasureSpec 获取的宽度 : 492 高度 836 ,
  10. * 默认 宽高 都是 120dip转化完毕后 180px
  11. *
  12. * 当将布局组件 的宽高设置为 240 dp :
  13. * 宽度 和 高度 MeasureSpec 获取的都是 360, 此时 MeasureSpec 属于精准模式
  14. *
  15. */
  16. setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));
  17. }


4. 图形绘制


(1) 设置画笔


画笔相关方法

-- 消除锯齿 : setAntiAlias(boolean);


  
  1. // 消除绘制时产生的锯齿
  2. mPaint.setAntiAlias(true);

-- 绘制空心圆设置的样式 : setStyle(Style.STROKE);

		// 绘制空心圆形需要设置该样式
		mPaint.setStyle(Style.STROKE);

-- 绘制实心图形文字需要设置的样式 : mPaint.setStrokeWidth(0);

-- 设置画笔颜色 : setColor(Color.BLUE);

-- 设置文字大小 : setTextSize(float);


  
  1. //设置数字的大小, 注意要根据 内圆半径设置
  2. mPaint.setTextSize(innerRadius / 2);


(2) 绘制图形


绘制圆 : canvas.drawCircle(float cx, float cy, float radius, Paint paint);

-- cx 参数 : 圆心的 x 轴距离;

-- cy 参数 : 圆心的 y 轴距离;

-- radius 参数 : 半径;

-- paint : 画笔;


绘制圆弧

-- 创建圆弧 : RectF rectf = new RectF(left, top, right, bottom);

-- 绘制 : canvas.drawArc(rectf, 270, mProcessValue, false, mPaint);

-- 示例


  
  1. //创建圆弧对象
  2. RectF rectf = new RectF(left, top, right, bottom);
  3. //绘制圆弧 参数介绍 : 圆弧, 开始度数, 累加度数, 是否闭合圆弧, 画笔
  4. canvas.drawArc(rectf, 270, mProcessValue, false, mPaint);

绘制文字



二. 代码示例


1. 自定义 View 代码



  
  1. package cn.org.octopus.circle;
  2. import android.content.Context;
  3. import android.graphics.Canvas;
  4. import android.graphics.Color;
  5. import android.graphics.Paint;
  6. import android.graphics.Paint.Style;
  7. import android.graphics.Rect;
  8. import android.graphics.RectF;
  9. import android.graphics.Typeface;
  10. import android.util.AttributeSet;
  11. import android.widget.ImageView;
  12. public class CircleProcess extends ImageView {
  13. /** 画笔 */
  14. private Paint mPaint;
  15. /** 上下文对象 */
  16. private Context mContext;
  17. /** 进度条的值 */
  18. private int mProcessValue;
  19. public CircleProcess(Context context, AttributeSet attrs) {
  20. super(context, attrs);
  21. // 初始化成员变量 Context
  22. mContext = context;
  23. // 创建画笔, 并设置画笔属性
  24. mPaint = new Paint();
  25. // 消除绘制时产生的锯齿
  26. mPaint.setAntiAlias(true);
  27. // 绘制空心圆形需要设置该样式
  28. mPaint.setStyle(Style.STROKE);
  29. }
  30. /**
  31. * 自定义布局实现的 只有 Context 参数的构造方法
  32. * @param context
  33. */
  34. public CircleProcess(Context context) {
  35. super(context);
  36. }
  37. /**
  38. * 自定义布局实现的 三个参数的构造方法
  39. * @param context
  40. * @param attrs
  41. * @param defStyle
  42. */
  43. public CircleProcess(Context context, AttributeSet attrs, int defStyle) {
  44. super(context, attrs, defStyle);
  45. }
  46. @Override
  47. protected void onDraw(Canvas canvas) {
  48. super.onDraw(canvas);
  49. //获取圆心的 x 轴位置
  50. int center = getWidth() / 2;
  51. /*
  52. * 中间位置 x 减去左侧位置 的绝对值就是圆半径,
  53. * 注意 : 由于 padding 属性存在, |left - right| 可能与 width 不同
  54. */
  55. int outerRadius = Math.abs(getLeft() - center);
  56. //计算内圆半径大小, 内圆半径 是 外圆半径的一般
  57. int innerRadius = outerRadius / 2;
  58. //设置画笔颜色
  59. mPaint.setColor(Color.BLUE);
  60. //设置画笔宽度
  61. mPaint.setStrokeWidth(2);
  62. //绘制内圆方法 前两个参数是 x, y 轴坐标, 第三个是内圆半径, 第四个参数是 画笔
  63. canvas.drawCircle(center, center, innerRadius, mPaint);
  64. /*
  65. * 绘制进度条的圆弧
  66. *
  67. * 绘制图形需要 left top right bottom 坐标, 下面需要计算这个坐标
  68. */
  69. //计算圆弧宽度
  70. int width = outerRadius - innerRadius;
  71. //将圆弧的宽度设置给 画笔
  72. mPaint.setStrokeWidth(width);
  73. /*
  74. * 计算画布绘制圆弧填入的 top left bottom right 值,
  75. * 这里注意给的值要在圆弧的一半位置, 绘制的时候参数是从中间开始绘制
  76. */
  77. int top = center - (innerRadius + width/2);
  78. int left = top;
  79. int bottom = center + (innerRadius + width/2);
  80. int right = bottom;
  81. //创建圆弧对象
  82. RectF rectf = new RectF(left, top, right, bottom);
  83. //绘制圆弧 参数介绍 : 圆弧, 开始度数, 累加度数, 是否闭合圆弧, 画笔
  84. canvas.drawArc(rectf, 270, mProcessValue, false, mPaint);
  85. //绘制外圆
  86. mPaint.setStrokeWidth(2);
  87. canvas.drawCircle(center, center, innerRadius + width, mPaint);
  88. /*
  89. * 在内部正中央绘制一个数字
  90. */
  91. //生成百分比数字
  92. String str = (int)(mProcessValue * 1.0 / 360 * 100) + "%";
  93. /*
  94. * 测量这个数字的宽 和 高
  95. */
  96. //创建数字的边界对象
  97. Rect textRect = new Rect();
  98. //设置数字的大小, 注意要根据 内圆半径设置
  99. mPaint.setTextSize(innerRadius / 2);
  100. mPaint.setStrokeWidth(0);
  101. //获取数字边界
  102. mPaint.getTextBounds(str, 0, str.length(), textRect);
  103. int textWidth = textRect.width();
  104. int textHeight = textRect.height();
  105. //根据数字大小获取绘制位置, 以便数字能够在正中央绘制出来
  106. int textX = center - textWidth / 2;
  107. int textY = center + textHeight / 2;
  108. //正式开始绘制数字
  109. canvas.drawText(str, textX, textY, mPaint);
  110. }
  111. @Override
  112. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  113. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  114. /*
  115. * setMeasuredDimension 方法 : 该方法决定当前的 View 的大小
  116. * 根据 View 在布局中的显示, 动态获取 View 的宽高
  117. *
  118. * 当布局组件 warp_content 时 :
  119. * 从 MeasureSpec 获取的宽度 : 492 高度 836 ,
  120. * 默认 宽高 都是 120dip转化完毕后 180px
  121. *
  122. * 当将布局组件 的宽高设置为 240 dp :
  123. * 宽度 和 高度 MeasureSpec 获取的都是 360, 此时 MeasureSpec 属于精准模式
  124. *
  125. */
  126. setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));
  127. }
  128. /**
  129. * 获取组件宽度
  130. *
  131. * MeasureSpec : 该 int 类型有 32 位, 前两位是状态位, 后面 30 位是大小值;
  132. * 常用方法 :
  133. * -- MeasureSpec.getMode(int) : 获取模式
  134. * -- MeasureSpec.getSize(int) : 获取大小
  135. * -- MeasureSpec.makeMeasureSpec(int size, int mode) : 创建一个 MeasureSpec;
  136. * -- MeasureSpec.toString(int) : 模式 + 大小 字符串
  137. *
  138. * 模式介绍 : 注意下面的数字是二进制的
  139. * -- 00 : MeasureSpec.UNSPECIFIED, 未指定模式;
  140. * -- 01 : MeasureSpec.EXACTLY, 精准模式;
  141. * -- 11 : MeasureSpec.AT_MOST, 最大模式;
  142. *
  143. * 注意 : 这个 MeasureSpec 模式是在 onMeasure 方法中自动生成的, 一般不用去创建这个对象
  144. *
  145. * @param widthMeasureSpec
  146. * MeasureSpec 数值
  147. * @return
  148. * 组件的宽度
  149. */
  150. private int measure(int measureSpec) {
  151. //返回的结果, 即组件宽度
  152. int result = 0;
  153. //获取组件的宽度模式
  154. int mode = MeasureSpec.getMode(measureSpec);
  155. //获取组件的宽度大小 单位px
  156. int size = MeasureSpec.getSize(measureSpec);
  157. if(mode == MeasureSpec.EXACTLY){//精准模式
  158. result = size;
  159. }else{//未定义模式 或者 最大模式
  160. //注意 200 是默认大小, 在 warp_content 时使用这个值, 如果组件中定义了大小, 就不使用该值
  161. result = dip2px(mContext, 200);
  162. if(mode == MeasureSpec.AT_MOST){//最大模式
  163. //最大模式下获取一个稍小的值
  164. result = Math.min(result, size);
  165. }
  166. }
  167. return result;
  168. }
  169. /**
  170. * 将手机的 设备独立像素 转为 像素值
  171. *
  172. * 公式 : px / dip = dpi / 160
  173. * px = dip * dpi / 160;
  174. * @param context
  175. * 上下文对象
  176. * @param dpValue
  177. * 设备独立像素值
  178. * @return
  179. * 转化后的 像素值
  180. */
  181. public static int dip2px(Context context, float dpValue) {
  182. final float scale = context.getResources().getDisplayMetrics().density;
  183. return (int) (dpValue * scale + 0.5f);
  184. }
  185. /**
  186. * 将手机的 像素值 转为 设备独立像素
  187. * 公式 : px/dip = dpi/160
  188. * dip = px * 160 / dpi
  189. * dpi (dot per inch) : 每英寸像素数 归一化的值 120 160 240 320 480;
  190. * density : 每英寸的像素数, 精准的像素数, 可以用来计算准确的值
  191. * 从 DisplayMetics 中获取的
  192. * @param context
  193. * 上下文对象
  194. * @param pxValue
  195. * 像素值
  196. * @return
  197. * 转化后的 设备独立像素值
  198. */
  199. public static int px2dip(Context context, float pxValue) {
  200. final float scale = context.getResources().getDisplayMetrics().density;
  201. return (int) (pxValue / scale + 0.5f);
  202. }
  203. /**
  204. * 获取当前进度值
  205. * @return
  206. * 返回当前进度值
  207. */
  208. public int getmProcessValue() {
  209. return mProcessValue;
  210. }
  211. /**
  212. * 为该组件设置进度值
  213. * @param mProcessValue
  214. * 设置的进度值参数
  215. */
  216. public void setmProcessValue(int mProcessValue) {
  217. this.mProcessValue = mProcessValue;
  218. }
  219. }


2. Activity 代码



  
  1. package cn.org.octopus.circle;
  2. import android.app.Activity;
  3. import android.app.ActionBar;
  4. import android.app.Fragment;
  5. import android.os.AsyncTask;
  6. import android.os.Bundle;
  7. import android.view.LayoutInflater;
  8. import android.view.Menu;
  9. import android.view.MenuItem;
  10. import android.view.View;
  11. import android.view.ViewGroup;
  12. import android.os.Build;
  13. public class MainActivity extends Activity {
  14. private static CircleProcess circle_process;
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.activity_main);
  19. //加载 Fragment
  20. if (savedInstanceState == null) {
  21. getFragmentManager().beginTransaction()
  22. .add(R.id.container, new PlaceholderFragment())
  23. .commit();
  24. }
  25. new CircleProcessAnimation().execute();
  26. }
  27. /**
  28. * 设置 异步任务, 在这个任务中 设置 圆形进度条的进度值
  29. * @author octopus
  30. *
  31. */
  32. class CircleProcessAnimation extends AsyncTask<Void, Integer, Void>{
  33. @Override
  34. protected Void doInBackground(Void... arg0) {
  35. for(int i = 1; i <= 360; i ++){
  36. try {
  37. //激活圆形进度条显示方法
  38. publishProgress(i);
  39. //每隔 50 毫秒更新一次数据
  40. Thread.sleep(50);
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. return null;
  46. }
  47. @Override
  48. protected void onProgressUpdate(Integer... values) {
  49. super.onProgressUpdate(values);
  50. //为圆形进度条组件设置进度值
  51. circle_process.setmProcessValue(values[0]);
  52. //刷新圆形进度条显示
  53. circle_process.invalidate();
  54. }
  55. }
  56. /**
  57. * 界面显示的 Fragment
  58. * @author octopus
  59. */
  60. public static class PlaceholderFragment extends Fragment {
  61. public PlaceholderFragment() {
  62. }
  63. @Override
  64. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  65. Bundle savedInstanceState) {
  66. View rootView = inflater.inflate(R.layout.fragment_main, container, false);
  67. circle_process = (CircleProcess) rootView.findViewById(R.id.circle_process);
  68. return rootView;
  69. }
  70. }
  71. @Override
  72. public boolean onCreateOptionsMenu(Menu menu) {
  73. getMenuInflater().inflate(R.menu.main, menu);
  74. return true;
  75. }
  76. @Override
  77. public boolean onOptionsItemSelected(MenuItem item) {
  78. int id = item.getItemId();
  79. if (id == R.id.action_settings) {
  80. return true;
  81. }
  82. return super.onOptionsItemSelected(item);
  83. }
  84. }


3. 布局文件代码 



  
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:paddingBottom="@dimen/activity_vertical_margin"
  6. android:paddingLeft="@dimen/activity_horizontal_margin"
  7. android:paddingRight="@dimen/activity_horizontal_margin"
  8. android:paddingTop="@dimen/activity_vertical_margin"
  9. tools:context="cn.org.octopus.circle.MainActivity$PlaceholderFragment"
  10. android:gravity="center">
  11. <cn.org.octopus.circle.CircleProcess
  12. android:id="@+id/circle_process"
  13. android:layout_width="300dip"
  14. android:layout_height="300dip"/>
  15. </RelativeLayout>



代码下载 : 

-- CSDN 下载地址 : http://download.csdn.net/detail/han1202012/8069497 ;

-- GitHub 地址 : https://github.com/han1202012/CircleProcess.git ;

-- 工程示例 : 




文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。

原文链接:hanshuliang.blog.csdn.net/article/details/40351487

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。