【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);
绘制文字
二. 代码示例
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 ;
-- 工程示例 :
文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。
原文链接:hanshuliang.blog.csdn.net/article/details/40351487
- 点赞
- 收藏
- 关注作者
评论(0)