公司的项目已经完成了几个了,无一例外,都有计步的功能,我的天哪,能不能有点创意,好吧,既然还要套代码,那么就把一些刁钻的UI给封装好,以后就可以直接使用提高效率了。说是刁钻,其实也并没有很夸张,只是原生控件实现不了而已。
这次的自定义View就是RoundProgressBar,顾名思义,圆形的ProgressBar而已,网上其实也有相关的博客,但我这里并不是简单的在View上面画个圆而已,我会稍微加点效果上去,尽量让它华丽点。
先上效果图:
好吧,步骤依然还是那几步:
1. 定义好attr属性,并在构造函数里将其初始化。
属性定义:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RoundProgressBar">
<attr name="max" format="integer"/>
<attr name="progress" format="integer"/>
<attr name="count" format="integer"/>
<attr name="countWidth" format="dimension"/>
<attr name="countAngle" format="float"/>
<attr name="countColor" format="color"/>
<attr name="secondCountColor" format="color"/>
<attr name="startAngle" format="float"/>
<attr name="spacingFromAngle" format="float"/>
<attr name="spacingToAngle" format="float"/>
<attr name="isSpacing" format="boolean"/>
<attr name="animation" format="boolean"/>
<attr name="animationDuration" format="integer"/>
</declare-styleable>
</resources>
属性注释:
//进度
private int progress;
//最大进度
private int max;
//矩形小块的个数
private int count;
//小块的宽度,实际应该叫高度才对,这个width实际是画弧线的strokeWidth而已
private int countWidth;
//小块所占的角度
private float countAngle;
//小块的颜色,相当于progressColor
private int countColor;
//小块的第二颜色,即进度颜色,相当于secondProgressColor
private int secondCountColor;
//进度的起始角度,即从progress从0开始的角度
private float startAngle;
//是否挖空部分进度
private boolean isSpacing;
//挖空的开始角度
private float spacingFromAngle;
//挖空的结束角度
private float spacingToAngle;
//是否使用动画更新progress
private boolean animation;
//动画的持续时间
private long animationDuration;
2. 重写onMeasure(),处理好宽高和默认宽高。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
//主要是使view的宽高一样,以及当wrap_content时默认为100dp
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int width=MeasureSpec.getSize(widthMeasureSpec);
if(widthMode==MeasureSpec.AT_MOST){
width=dp2px(100);
}
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
int height=MeasureSpec.getSize(heightMeasureSpec);
if(heightMode==MeasureSpec.AT_MOST){
height=dp2px(100);
}
setMeasuredDimension(Math.min(width, height), Math.min(width, height));
}
这里有点必须注意,使用LinearLayout作为父容器且值为指定值时上面代码测量高度是不会触发AT_MOST情况的,但使用RelativeLayout作为父容器时,即使是指定值或match_parent都会触发AT_MOST,是不是Bug有待进一步研究,先暂时用着LinearLayout先。
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
//每个小块之间的间隔角度
float countSplitAngle=0;
if(isSpacing){
//360度减去所有小块所占角度和挖空的总角度,并除以count-1,减1是因为间隔只有count-1个,这样首尾小块位置才对称
countSplitAngle=(360-count*countAngle-(spacingToAngle-spacingFromAngle))/(count-1);
}else{
//360度减去所有小块所占角度,并除以count,以整个360度作为进度
countSplitAngle=(360-count*countAngle)/count;
}
//这是画弧线需要的参数,即在该矩形内画弧线
RectF oval=new RectF(0+countWidth/2, 0+countWidth/2, getWidth()-countWidth/2, getHeight()-countWidth/2);
//设置小块的高度,即弧线的strokeWidth
paint.setStrokeWidth(countWidth);
paint.setStyle(Style.STROKE);
//先画进度,利用循环画弧把逐个小块画好,要把进度0点的起始角度加上
paint.setColor(secondCountColor);
for(int i=0; i<count*progress/max; i++){
float sweepStartAngle=startAngle+(countAngle+countSplitAngle)*i;
canvas.drawArc(oval, sweepStartAngle, countAngle, false, paint);
}
//换一种颜色把剩下的非进度小块画完
paint.setColor(countColor);
for(int i=count*progress/max; i<count; i++){
float sweepStartAngle=startAngle+(countAngle+countSplitAngle)*i;
canvas.drawArc(oval, sweepStartAngle, countAngle, false, paint);
}
//画个进度指针,这里随便画了一红色指针
paint.setColor(Color.RED);
if(isSpacing){
canvas.drawArc(oval, startAngle+(360-(spacingToAngle-spacingFromAngle))*progress/max, countAngle, false, paint);
}else{
canvas.drawArc(oval, startAngle+360*progress/max, countAngle, false, paint);
}
}
这里需要注意的是,若需要挖空处理,那么起始角度应该设置到挖空末尾位置的角度,这里默认是不挖空,其实角度是90度。
另一点需要注意的是,画弧函数的参数里涉及到度数,其度数的0度位于右边,和平常我们学过的几何坐标一样,但android里顺时针是正,和我们学过的几何坐标相反。
4. 在setProgress()里添加动画处理。
public void setProgress(int progress){
//若使用动画,则利用ValueAnimator进行产生一个进度加载的缓冲效果
if(animation){
ValueAnimator valueAnimator=ValueAnimator.ofInt(this.progress, progress);
valueAnimator.setDuration(animationDuration);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// TODO Auto-generated method stub
RoundProgressBar.this.progress=Integer.valueOf(animation.getAnimatedValue().toString());
//利用每一动画每帧改变progress/max,然后更新UI
invalidate();
}
});
valueAnimator.start();
}else{
this.progress=progress;
invalidate();
}
}
5. 布局文件添加自定义属性命名空间后使用该View。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/com.example.roundprogressbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.roundprogressbar.RoundProgressBar
android:id="@+id/progressBar"
android:layout_width="200dp"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:layout_marginTop="50dp"
custom:countWidth="10dp"
custom:count="100"
custom:animation="true"
custom:isSpacing="true"
custom:startAngle="135"
custom:animationDuration="2000"/>
<com.example.roundprogressbar.RoundProgressBar
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
custom:progress="40"
custom:countWidth="15dp"
custom:countAngle="2"
custom:count="80"
custom:countColor="#33ffffff"
custom:secondCountColor="#ffffffff"
android:background="#6495ED"/>
</LinearLayout>
贴上完整代码(布局文件在上面了):
RoundProgressBar.java
public class RoundProgressBar extends View {
//进度
private int progress;
//最大进度
private int max;
//矩形小块的个数
private int count;
//小块的宽度,实际应该叫高度才对,这个width实际是画弧线的strokeWidth而已
private int countWidth;
//小块所占的角度
private float countAngle;
//小块的颜色,相当于progressColor
private int countColor;
//小块的第二颜色,即进度颜色,相当于secondProgressColor
private int secondCountColor;
//进度的起始角度,即从progress从0开始的角度
private float startAngle;
//是否挖空部分进度
private boolean isSpacing;
//挖空的开始角度
private float spacingFromAngle;
//挖空的结束角度
private float spacingToAngle;
//是否使用动画更新progress
private boolean animation;
//动画的持续时间
private long animationDuration;
private Paint paint;
public RoundProgressBar(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}
public RoundProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO Auto-generated constructor stub
}
public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar);
progress=array.getInt(R.styleable.RoundProgressBar_progress, 0);
max=array.getInt(R.styleable.RoundProgressBar_max, 100);
count=array.getInt(R.styleable.RoundProgressBar_count, 100);
countWidth=array.getDimensionPixelSize(R.styleable.RoundProgressBar_countWidth, dp2px(10));
countAngle=array.getFloat(R.styleable.RoundProgressBar_countAngle, 1f);
countColor=array.getColor(R.styleable.RoundProgressBar_countColor, Color.GRAY);
secondCountColor=array.getColor(R.styleable.RoundProgressBar_secondCountColor, Color.GREEN);
startAngle=array.getFloat(R.styleable.RoundProgressBar_startAngle, 90f);
isSpacing=array.getBoolean(R.styleable.RoundProgressBar_isSpacing, false);
spacingFromAngle=array.getFloat(R.styleable.RoundProgressBar_spacingFromAngle, 45f);
spacingToAngle=array.getFloat(R.styleable.RoundProgressBar_spacingToAngle, 135f);
animation=array.getBoolean(R.styleable.RoundProgressBar_animation, false);
animationDuration=array.getInteger(R.styleable.RoundProgressBar_animationDuration, 2000);
array.recycle();
paint=new Paint();
paint.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
//主要是使view的宽高一样,以及当wrap_content时默认为100dp
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int width=MeasureSpec.getSize(widthMeasureSpec);Log.d("zz", "111:"+width);
if(widthMode==MeasureSpec.AT_MOST){
width=dp2px(100);Log.d("zz", "222:"+width);
}
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
int height=MeasureSpec.getSize(heightMeasureSpec);Log.d("zz", "333:"+height);
if(heightMode==MeasureSpec.AT_MOST){
height=dp2px(100);Log.d("zz", "444:"+height);
}
setMeasuredDimension(Math.min(width, height), Math.min(width, height));
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
//每个小块之间的间隔角度
float countSplitAngle=0;
if(isSpacing){
//360度减去所有小块所占角度和挖空的总角度,并除以count-1,减1是因为间隔只有count-1个,这样首尾小块位置才对称
countSplitAngle=(360-count*countAngle-(spacingToAngle-spacingFromAngle))/(count-1);
}else{
//360度减去所有小块所占角度,并除以count,以整个360度作为进度
countSplitAngle=(360-count*countAngle)/count;
}
//这是画弧线需要的参数,即在该矩形内画弧线
RectF oval=new RectF(0+countWidth/2, 0+countWidth/2, getWidth()-countWidth/2, getHeight()-countWidth/2);
//设置小块的高度,即弧线的strokeWidth
paint.setStrokeWidth(countWidth);
paint.setStyle(Style.STROKE);
//先画进度,利用循环画弧把逐个小块画好,要把进度0点的起始角度加上
paint.setColor(secondCountColor);
for(int i=0; i<count*progress/max; i++){
float sweepStartAngle=startAngle+(countAngle+countSplitAngle)*i;
canvas.drawArc(oval, sweepStartAngle, countAngle, false, paint);
}
//换一种颜色把剩下的非进度小块画完
paint.setColor(countColor);
for(int i=count*progress/max; i<count; i++){
float sweepStartAngle=startAngle+(countAngle+countSplitAngle)*i;
canvas.drawArc(oval, sweepStartAngle, countAngle, false, paint);
}
//画个进度指针,这里随便画了一红色指针
paint.setColor(Color.RED);
if(isSpacing){
canvas.drawArc(oval, startAngle+(360-(spacingToAngle-spacingFromAngle))*progress/max, countAngle, false, paint);
}else{
canvas.drawArc(oval, startAngle+360*progress/max, countAngle, false, paint);
}
}
public void setProgress(int progress){
//若使用动画,则利用ValueAnimator进行产生一个进度加载的缓冲效果
if(animation){
ValueAnimator valueAnimator=ValueAnimator.ofInt(this.progress, progress);
valueAnimator.setDuration(animationDuration);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// TODO Auto-generated method stub
RoundProgressBar.this.progress=Integer.valueOf(animation.getAnimatedValue().toString());
//利用每一动画每帧改变progress/max,然后更新UI
invalidate();
}
});
valueAnimator.start();
}else{
this.progress=progress;
invalidate();
}
}
//dp转px单位
private int dp2px(int dp){
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
}
MainActivity.java
public class MainActivity extends Activity {
private RoundProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar=(RoundProgressBar)findViewById(R.id.progressBar);
progressBar.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
progressBar.setProgress(80);
}
});
}
}
代码下载: github
---------------------------------程序员小记----------------------------------
昨天爬山,今天又在健身房苦练,好累好累,睡觉先,明天继续,加油~