【Android 应用开发】 自定义 圆形进度条 组件
转载著名出处 :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参数构造方法中实现逻辑;
构造方法示例 :
/** 画笔 */ private Paint mPaint; /** 上下文对象 */ private Context mContext; /** 进度条的值 */ private int mProcessValue; public CircleProcess(Context context, AttributeSet attrs) { super(context, attrs); // 初始化成员变量 Context mContext = context; // 创建画笔, 并设置画笔属性 mPaint = new Paint(); // 消除绘制时产生的锯齿 mPaint.setAntiAlias(true); // 绘制空心圆形需要设置该样式 mPaint.setStyle(Style.STROKE); } /** * 自定义布局实现的 只有 Context 参数的构造方法 * @param context */ public CircleProcess(Context context) { super(context); } /** * 自定义布局实现的 三个参数的构造方法 * @param context * @param attrs * @param defStyle */ public CircleProcess(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); }
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 是实际的值, 可能不是整数;
代码示例 :
/** * 将手机的 设备独立像素 转为 像素值 * * 公式 : px / dip = dpi / 160 * px = dip * dpi / 160; * @param context * 上下文对象 * @param dpValue * 设备独立像素值 * @return * 转化后的 像素值 */ public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); }
(2) px 转 dip
公式 :
-- 基本公式 : px / dip = dpi / 160;
-- 计算公式 : dip = 160 / dpi * px;
代码 :
/** * 将手机的 像素值 转为 设备独立像素 * 公式 : px/dip = dpi/160 * dip = px * 160 / dpi * dpi (dot per inch) : 每英寸像素数 归一化的值 120 160 240 320 480; * density : 每英寸的像素数, 精准的像素数, 可以用来计算准确的值 * 从 DisplayMetics 中获取的 * @param context * 上下文对象 * @param pxValue * 像素值 * @return * 转化后的 设备独立像素值 */ public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); }
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 中的较小的那个;
-- 未定义模式 : 默认大小;
通用计算方法代码 :
/** * 获取组件宽度 * * MeasureSpec : 该 int 类型有 32 位, 前两位是状态位, 后面 30 位是大小值; * 常用方法 : * -- MeasureSpec.getMode(int) : 获取模式 * -- MeasureSpec.getSize(int) : 获取大小 * -- MeasureSpec.makeMeasureSpec(int size, int mode) : 创建一个 MeasureSpec; * -- MeasureSpec.toString(int) : 模式 + 大小 字符串 * * 模式介绍 : 注意下面的数字是二进制的 * -- 00 : MeasureSpec.UNSPECIFIED, 未指定模式; * -- 01 : MeasureSpec.EXACTLY, 精准模式; * -- 11 : MeasureSpec.AT_MOST, 最大模式; * * 注意 : 这个 MeasureSpec 模式是在 onMeasure 方法中自动生成的, 一般不用去创建这个对象 * * @param widthMeasureSpec * MeasureSpec 数值 * @return * 组件的宽度 */ private int measure(int measureSpec) { //返回的结果, 即组件宽度 int result = 0; //获取组件的宽度模式 int mode = MeasureSpec.getMode(measureSpec); //获取组件的宽度大小 单位px int size = MeasureSpec.getSize(measureSpec); if(mode == MeasureSpec.EXACTLY){//精准模式 result = size; }else{//未定义模式 或者 最大模式 //注意 200 是默认大小, 在 warp_content 时使用这个值, 如果组件中定义了大小, 就不使用该值 result = dip2px(mContext, 200); if(mode == MeasureSpec.AT_MOST){//最大模式 //最大模式下获取一个稍小的值 result = Math.min(result, size); } } return result; }
(3) 设置 组件大小方法
setMeasuredDimension() 方法 : 该方法决定 View 组件的大小;
-- 使用场所 : 在 onMeasure() 方法中调用该方法, 就设置了组件的宽 和 高, 然后在其它位置调用 getWidth() 和 getHeight() 方法时, 获取的就是 该方法设置的值;
-- 代码示例 :
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); /* * setMeasuredDimension 方法 : 该方法决定当前的 View 的大小 * 根据 View 在布局中的显示, 动态获取 View 的宽高 * * 当布局组件 warp_content 时 : * 从 MeasureSpec 获取的宽度 : 492 高度 836 , * 默认 宽高 都是 120dip转化完毕后 180px * * 当将布局组件 的宽高设置为 240 dp : * 宽度 和 高度 MeasureSpec 获取的都是 360, 此时 MeasureSpec 属于精准模式 * */ setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec)); }
4. 图形绘制
(1) 设置画笔
画笔相关方法 :
-- 消除锯齿 :setAntiAlias(boolean);
// 消除绘制时产生的锯齿 mPaint.setAntiAlias(true);
-- 绘制空心圆设置的样式 :setStyle(Style.STROKE);
// 绘制空心圆形需要设置该样式 mPaint.setStyle(Style.STROKE);
-- 绘制实心图形文字需要设置的样式 :mPaint.setStrokeWidth(0);
-- 设置画笔颜色 :setColor(Color.BLUE);
-- 设置文字大小 :setTextSize(float);
//设置数字的大小, 注意要根据 内圆半径设置 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);
-- 示例 :
//创建圆弧对象 RectF rectf = new RectF(left, top, right, bottom); //绘制圆弧 参数介绍 : 圆弧, 开始度数, 累加度数, 是否闭合圆弧, 画笔 canvas.drawArc(rectf, 270, mProcessValue, false, mPaint);
绘制文字 :canvas.drawText(str, textX, textY, mPaint);
二. 代码示例
1. 自定义 View 代码
package cn.org.octopus.circle; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.util.AttributeSet; import android.widget.ImageView; public class CircleProcess extends ImageView { /** 画笔 */ private Paint mPaint; /** 上下文对象 */ private Context mContext; /** 进度条的值 */ private int mProcessValue; public CircleProcess(Context context, AttributeSet attrs) { super(context, attrs); // 初始化成员变量 Context mContext = context; // 创建画笔, 并设置画笔属性 mPaint = new Paint(); // 消除绘制时产生的锯齿 mPaint.setAntiAlias(true); // 绘制空心圆形需要设置该样式 mPaint.setStyle(Style.STROKE); } /** * 自定义布局实现的 只有 Context 参数的构造方法 * @param context */ public CircleProcess(Context context) { super(context); } /** * 自定义布局实现的 三个参数的构造方法 * @param context * @param attrs * @param defStyle */ public CircleProcess(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //获取圆心的 x 轴位置 int center = getWidth() / 2; /* * 中间位置 x 减去左侧位置 的绝对值就是圆半径, * 注意 : 由于 padding 属性存在, |left - right| 可能与 width 不同 */ int outerRadius = Math.abs(getLeft() - center); //计算内圆半径大小, 内圆半径 是 外圆半径的一般 int innerRadius = outerRadius / 2; //设置画笔颜色 mPaint.setColor(Color.BLUE); //设置画笔宽度 mPaint.setStrokeWidth(2); //绘制内圆方法 前两个参数是 x, y 轴坐标, 第三个是内圆半径, 第四个参数是 画笔 canvas.drawCircle(center, center, innerRadius, mPaint); /* * 绘制进度条的圆弧 * * 绘制图形需要 left top right bottom 坐标, 下面需要计算这个坐标 */ //计算圆弧宽度 int width = outerRadius - innerRadius; //将圆弧的宽度设置给 画笔 mPaint.setStrokeWidth(width); /* * 计算画布绘制圆弧填入的 top left bottom right 值, * 这里注意给的值要在圆弧的一半位置, 绘制的时候参数是从中间开始绘制 */ int top = center - (innerRadius + width/2); int left = top; int bottom = center + (innerRadius + width/2); int right = bottom; //创建圆弧对象 RectF rectf = new RectF(left, top, right, bottom); //绘制圆弧 参数介绍 : 圆弧, 开始度数, 累加度数, 是否闭合圆弧, 画笔 canvas.drawArc(rectf, 270, mProcessValue, false, mPaint); //绘制外圆 mPaint.setStrokeWidth(2); canvas.drawCircle(center, center, innerRadius + width, mPaint); /* * 在内部正中央绘制一个数字 */ //生成百分比数字 String str = (int)(mProcessValue * 1.0 / 360 * 100) + "%"; /* * 测量这个数字的宽 和 高 */ //创建数字的边界对象 Rect textRect = new Rect(); //设置数字的大小, 注意要根据 内圆半径设置 mPaint.setTextSize(innerRadius / 2); mPaint.setStrokeWidth(0); //获取数字边界 mPaint.getTextBounds(str, 0, str.length(), textRect); int textWidth = textRect.width(); int textHeight = textRect.height(); //根据数字大小获取绘制位置, 以便数字能够在正中央绘制出来 int textX = center - textWidth / 2; int textY = center + textHeight / 2; //正式开始绘制数字 canvas.drawText(str, textX, textY, mPaint); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); /* * setMeasuredDimension 方法 : 该方法决定当前的 View 的大小 * 根据 View 在布局中的显示, 动态获取 View 的宽高 * * 当布局组件 warp_content 时 : * 从 MeasureSpec 获取的宽度 : 492 高度 836 , * 默认 宽高 都是 120dip转化完毕后 180px * * 当将布局组件 的宽高设置为 240 dp : * 宽度 和 高度 MeasureSpec 获取的都是 360, 此时 MeasureSpec 属于精准模式 * */ setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec)); } /** * 获取组件宽度 * * MeasureSpec : 该 int 类型有 32 位, 前两位是状态位, 后面 30 位是大小值; * 常用方法 : * -- MeasureSpec.getMode(int) : 获取模式 * -- MeasureSpec.getSize(int) : 获取大小 * -- MeasureSpec.makeMeasureSpec(int size, int mode) : 创建一个 MeasureSpec; * -- MeasureSpec.toString(int) : 模式 + 大小 字符串 * * 模式介绍 : 注意下面的数字是二进制的 * -- 00 : MeasureSpec.UNSPECIFIED, 未指定模式; * -- 01 : MeasureSpec.EXACTLY, 精准模式; * -- 11 : MeasureSpec.AT_MOST, 最大模式; * * 注意 : 这个 MeasureSpec 模式是在 onMeasure 方法中自动生成的, 一般不用去创建这个对象 * * @param widthMeasureSpec * MeasureSpec 数值 * @return * 组件的宽度 */ private int measure(int measureSpec) { //返回的结果, 即组件宽度 int result = 0; //获取组件的宽度模式 int mode = MeasureSpec.getMode(measureSpec); //获取组件的宽度大小 单位px int size = MeasureSpec.getSize(measureSpec); if(mode == MeasureSpec.EXACTLY){//精准模式 result = size; }else{//未定义模式 或者 最大模式 //注意 200 是默认大小, 在 warp_content 时使用这个值, 如果组件中定义了大小, 就不使用该值 result = dip2px(mContext, 200); if(mode == MeasureSpec.AT_MOST){//最大模式 //最大模式下获取一个稍小的值 result = Math.min(result, size); } } return result; } /** * 将手机的 设备独立像素 转为 像素值 * * 公式 : px / dip = dpi / 160 * px = dip * dpi / 160; * @param context * 上下文对象 * @param dpValue * 设备独立像素值 * @return * 转化后的 像素值 */ public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } /** * 将手机的 像素值 转为 设备独立像素 * 公式 : px/dip = dpi/160 * dip = px * 160 / dpi * dpi (dot per inch) : 每英寸像素数 归一化的值 120 160 240 320 480; * density : 每英寸的像素数, 精准的像素数, 可以用来计算准确的值 * 从 DisplayMetics 中获取的 * @param context * 上下文对象 * @param pxValue * 像素值 * @return * 转化后的 设备独立像素值 */ public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } /** * 获取当前进度值 * @return * 返回当前进度值 */ public int getmProcessValue() { return mProcessValue; } /** * 为该组件设置进度值 * @param mProcessValue * 设置的进度值参数 */ public void setmProcessValue(int mProcessValue) { this.mProcessValue = mProcessValue; } }
2. Activity 代码
package cn.org.octopus.circle; import android.app.Activity; import android.app.ActionBar; import android.app.Fragment; import android.os.AsyncTask; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.os.Build; public class MainActivity extends Activity { private static CircleProcess circle_process; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //加载 Fragment if (savedInstanceState == null) { getFragmentManager().beginTransaction() .add(R.id.container, new PlaceholderFragment()) .commit(); } new CircleProcessAnimation().execute(); } /** * 设置 异步任务, 在这个任务中 设置 圆形进度条的进度值 * @author octopus * */ class CircleProcessAnimation extends AsyncTask<Void, Integer, Void>{ @Override protected Void doInBackground(Void... arg0) { for(int i = 1; i <= 360; i ++){ try { //激活圆形进度条显示方法 publishProgress(i); //每隔 50 毫秒更新一次数据 Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } return null; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); //为圆形进度条组件设置进度值 circle_process.setmProcessValue(values[0]); //刷新圆形进度条显示 circle_process.invalidate(); } } /** * 界面显示的 Fragment * @author octopus */ public static class PlaceholderFragment extends Fragment { public PlaceholderFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); circle_process = (CircleProcess) rootView.findViewById(R.id.circle_process); return rootView; } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
3. 布局文件代码
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="cn.org.octopus.circle.MainActivity$PlaceholderFragment" android:gravity="center"> <cn.org.octopus.circle.CircleProcess android:id="@+id/circle_process" android:layout_width="300dip" android:layout_height="300dip"/> </RelativeLayout>
代码下载:
--CSDN 下载地址:http://download.csdn.net/detail/han1202012/8069497;
--GitHub 地址:https://github.com/han1202012/CircleProcess.git;
--工程示例:
相关推荐
综上所述,实现一个自定义圆形进度条涉及Android自定义视图的基本原理,包括绘制、动画、属性支持以及性能优化等方面的知识。通过这个过程,开发者不仅可以掌握自定义组件的开发技巧,还能增强对Android图形绘制和UI...
Android社区提供了许多优秀的第三方库,例如CircleProgress或ArcProgressStackView,它们提供丰富的样式和动画效果,使开发者能快速实现自定义圆形进度条。 1. 添加依赖库到build.gradle文件: ```groovy ...
对于"android自定义圆形进度条",我们可以从以下几个方面来实现: 1. **继承View或ProgressBar**: - 选择直接继承View,意味着我们需要手动实现绘制进度条的逻辑,包括圆形轮廓和进度填充部分。 - 如果选择继承...
最后,将这个自定义的圆形进度条组件集成到布局文件中,通过XML属性或者代码方式设置初始进度和中间的图像资源,就可以在智能家居应用的界面上看到生动且富有个性的进度反馈了。 综上所述,自定义圆形进度条中间...
"Android漂亮的自定义圆形进度条源码.zip"是一个包含源代码的压缩包,它提供了一个定制的圆形进度条组件,用于替代系统默认的线性或圆形进度条。这个自定义组件可能包含了以下关键知识点: 1. 自定义View:在...
"自定义圆形进度条"(circleProgressbar)就是这样的一个实例,它允许开发者根据特定需求创建具有个性化外观和行为的进度条组件。这个自定义视图通常用于展示某种任务或进程的完成程度,比如加载、刷新或者计时等...
总之,"Android高级应用源码-漂亮的自定义圆形进度条"提供了丰富的实践案例,适合希望提高Android自定义视图开发技能的开发者学习。通过深入研究并实践这段源码,开发者不仅可以掌握自定义圆形进度条的实现,还能...
这个"漂亮的自定义圆形进度条"源码学习项目,不仅有助于理解Android自定义View的原理,还可以提升开发者在UI设计和动画实现上的技能。通过研究和实践,你可以掌握如何在Android应用中创建出符合自己需求的特色组件,...
首先,我们需要创建一个新的Java类,继承自Android的View类,作为自定义圆形进度条的基础。在`ProgressDemo`项目中,这个类可能被命名为`CircleProgressBar.java`。在该类中,我们需要重写以下关键方法: 1. **构造...
这里我们将基于`View`来创建自定义圆形进度条。 1. 创建一个新的Java类,继承自`View`: ```java public class CustomCircleProgressBar extends View { // 初始化属性,如颜色、半径、进度等 } ``` 2. 在`onDraw...
这个"Android应用源码之漂亮的自定义圆形进度条"的示例,可以帮助开发者深入理解Android自定义视图的原理和实践,同时也能提供一个可复用的组件,用于项目的快速开发。通过学习和参考这个源码,开发者可以进一步提升...
一、自定义圆形进度条的基本概念 自定义圆形进度条通常涉及以下关键点: 1. 绘制:利用Android的`View`或`ViewGroup`作为基类,重写`onDraw()`方法,在这个方法内使用`Canvas`进行绘制。我们可以使用`Path`来创建...
"安卓进度条loadingprogress相关-Android自定义圆形进度条效果.rar"这个压缩包文件显然包含了一个关于自定义圆形进度条的示例项目。圆形进度条在很多应用中都有广泛的应用,如加载数据、下载文件或网络请求时显示...
"安卓Android源码——漂亮的自定义圆形进度条.rar" 提供的正是一个这样的实例,它展示了如何在Android平台上创建一个美观且可定制的圆形进度条组件。这个压缩包中的"ProgressWheel-master"项目包含了实现这一功能的...
"Android高级应用源码-漂亮的自定义圆形进度条.zip" 提供的正是一个这样的实例,它展示了一个美观且可定制的圆形进度条组件。这个组件名为ProgressWheel,通常用于显示加载或进度信息,为用户界面增添了动态效果和...
创建一个自定义圆形进度条,我们需要继承Android的基础View类,如`View`或`ProgressBar`,然后重写其关键方法,如`onDraw()`。在`onDraw()`方法中,我们将绘制圆形轮廓以及百分比填充部分。我们可以利用`Canvas`对象...