Android 打造形形色色的进度条 实现可以如此简单

1、概述

最近需要用进度条,秉着不重复造轮子的原则,上github上搜索了一番,看了几个觉得比较好看的ProgressBar,比如:daimajia的等。简单看了下代码,基本都是继承自View,彻彻底底的自定义了一个进度条。盯着那绚丽滚动条,忽然觉得,为什么要通过View去写一个滚动条,系统已经提供了ProgressBar以及属于它的特性,我们没必要重新去构建一个,但是系统的又比较丑,不同版本变现还不一定一样。那么得出我们的目标:改变系统ProgressBar的样子。 

  对没错,我们没有必要去从0打造一个ProgressBar,人家虽然长的不好看,但是特性以及稳定性还是刚刚的,我们只需要为其整下容就ok了。 

说到整容,大家都知道我们的控件是通过onDraw()画出来的,那么我们只需要去覆盖它的onDraw()方法,自己写下就ok 。 

对了,我创建了一个微信公众号,欢迎关注,左边栏目上扫一扫即可。

  接下来,我们贴下效果图:

2、效果图

1、横向的进度条

Android 打造形形色色的进度条 实现可以如此简单_第1张图片


2、圆形的进度条

Android 打造形形色色的进度条 实现可以如此简单_第2张图片

没错,这就是我们的进度条效果,横向的模仿了daimajia的进度条样子。不过我们继承子ProgressBar,简单的为其整个容,代码清晰易懂 。为什么说,易懂呢?

横向那个进度条,大家会drawLine()和drawText()吧,那么通过getWidth()拿到控件的宽度,再通过getProgress()拿到进度,按比例控制绘制线的长短,字的位置还不是分分钟的事。

github源码地址:Android-ProgressBarWidthNumber欢迎大家star or fork 。


3、实现

横向的滚动条绘制肯定需要一些属性,比如已/未到达进度的颜色、宽度,文本的颜色、大小等。 
本来呢,我是想通过系统ProgressBar的progressDrawable,从里面提取一些属性完成绘制需要的参数的。但是,最终呢,反而让代码变得复杂。所以最终还是改用自定义属性。 说道自定义属性,大家应该已经不陌生了。

1、HorizontalProgressBarWithNumber

1、自定义属性

values/attr_progress_bar.xml:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.   
  4.     <declare-styleable name="HorizontalProgressBarWithNumber">  
  5.         <attr name="progress_unreached_color" format="color" />  
  6.         <attr name="progress_reached_color" format="color" />  
  7.         <attr name="progress_reached_bar_height" format="dimension" />  
  8.         <attr name="progress_unreached_bar_height" format="dimension" />  
  9.         <attr name="progress_text_size" format="dimension" />  
  10.         <attr name="progress_text_color" format="color" />  
  11.         <attr name="progress_text_offset" format="dimension" />  
  12.         <attr name="progress_text_visibility" format="enum">  
  13.             <enum name="visible" value="0" />  
  14.             <enum name="invisible" value="1" />  
  15.         </attr>  
  16.     </declare-styleable>  
  17.       
  18.     <declare-styleable name="RoundProgressBarWidthNumber">  
  19.         <attr name="radius" format="dimension" />  
  20.     </declare-styleable>  
  21.   
  22. </resources>  

2、构造中获取

[java]  view plain  copy
 
  1. public class HorizontalProgressBarWithNumber extends ProgressBar  
  2. {  
  3.   
  4.     private static final int DEFAULT_TEXT_SIZE = 10;  
  5.     private static final int DEFAULT_TEXT_COLOR = 0XFFFC00D1;  
  6.     private static final int DEFAULT_COLOR_UNREACHED_COLOR = 0xFFd3d6da;  
  7.     private static final int DEFAULT_HEIGHT_REACHED_PROGRESS_BAR = 2;  
  8.     private static final int DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR = 2;  
  9.     private static final int DEFAULT_SIZE_TEXT_OFFSET = 10;  
  10.   
  11.     /** 
  12.      * painter of all drawing things 
  13.      */  
  14.     protected Paint mPaint = new Paint();  
  15.     /** 
  16.      * color of progress number 
  17.      */  
  18.     protected int mTextColor = DEFAULT_TEXT_COLOR;  
  19.     /** 
  20.      * size of text (sp) 
  21.      */  
  22.     protected int mTextSize = sp2px(DEFAULT_TEXT_SIZE);  
  23.   
  24.     /** 
  25.      * offset of draw progress 
  26.      */  
  27.     protected int mTextOffset = dp2px(DEFAULT_SIZE_TEXT_OFFSET);  
  28.   
  29.     /** 
  30.      * height of reached progress bar 
  31.      */  
  32.     protected int mReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_REACHED_PROGRESS_BAR);  
  33.   
  34.     /** 
  35.      * color of reached bar 
  36.      */  
  37.     protected int mReachedBarColor = DEFAULT_TEXT_COLOR;  
  38.     /** 
  39.      * color of unreached bar 
  40.      */  
  41.     protected int mUnReachedBarColor = DEFAULT_COLOR_UNREACHED_COLOR;  
  42.     /** 
  43.      * height of unreached progress bar 
  44.      */  
  45.     protected int mUnReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR);  
  46.     /** 
  47.      * view width except padding 
  48.      */  
  49.     protected int mRealWidth;  
  50.       
  51.     protected boolean mIfDrawText = true;  
  52.   
  53.     protected static final int VISIBLE = 0;  
  54.   
  55.     public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs)  
  56.     {  
  57.         this(context, attrs, 0);  
  58.     }  
  59.   
  60.     public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs,  
  61.             int defStyle)  
  62.     {  
  63.         super(context, attrs, defStyle);  
  64.           
  65.         setHorizontalScrollBarEnabled(true);  
  66.   
  67.         obtainStyledAttributes(attrs);  
  68.   
  69.         mPaint.setTextSize(mTextSize);  
  70.         mPaint.setColor(mTextColor);  
  71.   
  72.     }  
  73.       
  74.     /** 
  75.      * get the styled attributes 
  76.      *  
  77.      * @param attrs 
  78.      */  
  79.     private void obtainStyledAttributes(AttributeSet attrs)  
  80.     {  
  81.         // init values from custom attributes  
  82.         final TypedArray attributes = getContext().obtainStyledAttributes(  
  83.                 attrs, R.styleable.HorizontalProgressBarWithNumber);  
  84.   
  85.         mTextColor = attributes  
  86.                 .getColor(  
  87.                         R.styleable.HorizontalProgressBarWithNumber_progress_text_color,  
  88.                         DEFAULT_TEXT_COLOR);  
  89.         mTextSize = (int) attributes.getDimension(  
  90.                 R.styleable.HorizontalProgressBarWithNumber_progress_text_size,  
  91.                 mTextSize);  
  92.   
  93.         mReachedBarColor = attributes  
  94.                 .getColor(  
  95.                         R.styleable.HorizontalProgressBarWithNumber_progress_reached_color,  
  96.                         mTextColor);  
  97.         mUnReachedBarColor = attributes  
  98.                 .getColor(  
  99.                         R.styleable.HorizontalProgressBarWithNumber_progress_unreached_color,  
  100.                         DEFAULT_COLOR_UNREACHED_COLOR);  
  101.         mReachedProgressBarHeight = (int) attributes  
  102.                 .getDimension(  
  103.                         R.styleable.HorizontalProgressBarWithNumber_progress_reached_bar_height,  
  104.                         mReachedProgressBarHeight);  
  105.         mUnReachedProgressBarHeight = (int) attributes  
  106.                 .getDimension(  
  107.                         R.styleable.HorizontalProgressBarWithNumber_progress_unreached_bar_height,  
  108.                         mUnReachedProgressBarHeight);  
  109.         mTextOffset = (int) attributes  
  110.                 .getDimension(  
  111.                         R.styleable.HorizontalProgressBarWithNumber_progress_text_offset,  
  112.                         mTextOffset);  
  113.   
  114.         int textVisible = attributes  
  115.                 .getInt(R.styleable.HorizontalProgressBarWithNumber_progress_text_visibility,  
  116.                         VISIBLE);  
  117.         if (textVisible != VISIBLE)  
  118.         {  
  119.             mIfDrawText = false;  
  120.         }  
  121.         attributes.recycle();  
  122.     }  

嗯,看起来代码挺长,其实都是在获取自定义属性,没什么技术含量。


3、onMeasure

刚才不是出onDraw里面写写就行了么,为什么要改onMeasure呢,主要是因为我们所有的属性比如进度条宽度让用户自定义了,所以我们的测量也得稍微变下。

[java]  view plain  copy
 
  1. @Override  
  2. protected synchronized void onMeasure(int widthMeasureSpec,  
  3.         int heightMeasureSpec)  
  4. {  
  5.     int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  6.   
  7.     if (heightMode != MeasureSpec.EXACTLY)  
  8.     {  
  9.   
  10.         float textHeight = (mPaint.descent() + mPaint.ascent());  
  11.         int exceptHeight = (int) (getPaddingTop() + getPaddingBottom() + Math  
  12.                 .max(Math.max(mReachedProgressBarHeight,  
  13.                         mUnReachedProgressBarHeight), Math.abs(textHeight)));  
  14.   
  15.         heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight,  
  16.                 MeasureSpec.EXACTLY);  
  17.     }  
  18.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  19.   
  20. }  

宽度我们不变,所以的自定义属性不涉及宽度,高度呢,只考虑不是EXACTLY的情况(用户明确指定了,我们就不管了),根据padding和进度条宽度算出自己想要的,如果非EXACTLY下,我们进行exceptHeight封装,传入给控件进行测量高度。

测量完,就到我们的onDraw了~~~

4、onDraw

[java]  view plain  copy
 
  1. @Override  
  2.     protected synchronized void onDraw(Canvas canvas)  
  3.     {  
  4.         canvas.save();  
  5.         //画笔平移到指定paddingLeft, getHeight() / 2位置,注意以后坐标都为以此为0,0  
  6.         canvas.translate(getPaddingLeft(), getHeight() / 2);  
  7.   
  8.         boolean noNeedBg = false;  
  9.         //当前进度和总值的比例  
  10.         float radio = getProgress() * 1.0f / getMax();  
  11.         //已到达的宽度  
  12.         float progressPosX = (int) (mRealWidth * radio);  
  13.         //绘制的文本  
  14.         String text = getProgress() + "%";  
  15.   
  16.         //拿到字体的宽度和高度  
  17.         float textWidth = mPaint.measureText(text);  
  18.         float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;  
  19.   
  20.         //如果到达最后,则未到达的进度条不需要绘制  
  21.         if (progressPosX + textWidth > mRealWidth)  
  22.         {  
  23.             progressPosX = mRealWidth - textWidth;  
  24.             noNeedBg = true;  
  25.         }  
  26.   
  27.         // 绘制已到达的进度  
  28.         float endX = progressPosX - mTextOffset / 2;  
  29.         if (endX > 0)  
  30.         {  
  31.             mPaint.setColor(mReachedBarColor);  
  32.             mPaint.setStrokeWidth(mReachedProgressBarHeight);  
  33.             canvas.drawLine(00, endX, 0, mPaint);  
  34.         }  
  35.       
  36.         // 绘制文本  
  37.         if (mIfDrawText)  
  38.         {  
  39.             mPaint.setColor(mTextColor);  
  40.             canvas.drawText(text, progressPosX, -textHeight, mPaint);  
  41.         }  
  42.   
  43.         // 绘制未到达的进度条  
  44.         if (!noNeedBg)  
  45.         {  
  46.             float start = progressPosX + mTextOffset / 2 + textWidth;  
  47.             mPaint.setColor(mUnReachedBarColor);  
  48.             mPaint.setStrokeWidth(mUnReachedProgressBarHeight);  
  49.             canvas.drawLine(start, 0, mRealWidth, 0, mPaint);  
  50.         }  
  51.   
  52.         canvas.restore();  
  53.   
  54.     }  
  55.   
  56.     @Override  
  57.     protected void onSizeChanged(int w, int h, int oldw, int oldh)  
  58.     {  
  59.         super.onSizeChanged(w, h, oldw, oldh);  
  60.         mRealWidth = w - getPaddingRight() - getPaddingLeft();  
  61.   
  62.     }  

其实核心方法就是onDraw了,但是呢,onDraw也很简单,绘制线、绘制文本、绘制线,结束。


还有两个简单的辅助方法:

[java]  view plain  copy
 
  1. /** 
  2.  * dp 2 px 
  3.  *  
  4.  * @param dpVal 
  5.  */  
  6. protected int dp2px(int dpVal)  
  7. {  
  8.     return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
  9.             dpVal, getResources().getDisplayMetrics());  
  10. }  
  11.   
  12. /** 
  13.  * sp 2 px 
  14.  *  
  15.  * @param spVal 
  16.  * @return 
  17.  */  
  18. protected int sp2px(int spVal)  
  19. {  
  20.     return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,  
  21.             spVal, getResources().getDisplayMetrics());  
  22.   
  23. }  

好了,到此我们的横向进度就结束了,是不是很简单~~如果你是自定义View,你还得考虑progress的更新,考虑状态的销毁与恢复等等复杂的东西。

接下来看我们的RoundProgressBarWidthNumber圆形的进度条。

2、RoundProgressBarWidthNumber 

圆形的进度条和横向的进度条基本变量都是一致的,于是我就让RoundProgressBarWidthNumber extends HorizontalProgressBarWithNumber 了。

然后需要改变的就是测量和onDraw了:

完整代码:

[java]  view plain  copy
 
  1. package com.zhy.view;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.graphics.Canvas;  
  6. import android.graphics.Paint.Cap;  
  7. import android.graphics.Paint.Style;  
  8. import android.graphics.RectF;  
  9. import android.util.AttributeSet;  
  10.   
  11. import com.zhy.library.view.R;  
  12.   
  13. public class RoundProgressBarWidthNumber extends  
  14.         HorizontalProgressBarWithNumber {  
  15.     /** 
  16.      * mRadius of view 
  17.      */  
  18.     private int mRadius = dp2px(30);  
  19.   
  20.     public RoundProgressBarWidthNumber(Context context) {  
  21.         this(context, null);  
  22.     }  
  23.   
  24.     public RoundProgressBarWidthNumber(Context context, AttributeSet attrs) {  
  25.         super(context, attrs);  
  26.   
  27.         mReachedProgressBarHeight = (int) (mUnReachedProgressBarHeight * 2.5f);  
  28.         TypedArray ta = context.obtainStyledAttributes(attrs,  
  29.                 R.styleable.RoundProgressBarWidthNumber);  
  30.         mRadius = (int) ta.getDimension(  
  31.                 R.styleable.RoundProgressBarWidthNumber_radius, mRadius);  
  32.         ta.recycle();  
  33.   
  34.         mTextSize = sp2px(14);  
  35.   
  36.         mPaint.setStyle(Style.STROKE);  
  37.         mPaint.setAntiAlias(true);  
  38.         mPaint.setDither(true);  
  39.         mPaint.setStrokeCap(Cap.ROUND);  
  40.   
  41.     }  
  42.   
  43.     @Override  
  44.     protected synchronized void onMeasure(int widthMeasureSpec,  
  45.             int heightMeasureSpec) {  
  46.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  47.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  48.   
  49.         int paintWidth = Math.max(mReachedProgressBarHeight,  
  50.                 mUnReachedProgressBarHeight);  
  51.   
  52.         if (heightMode != MeasureSpec.EXACTLY) {  
  53.   
  54.             int exceptHeight = (int) (getPaddingTop() + getPaddingBottom()  
  55.                     + mRadius * 2 + paintWidth);  
  56.             heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight,  
  57.                     MeasureSpec.EXACTLY);  
  58.         }  
  59.         if (widthMode != MeasureSpec.EXACTLY) {  
  60.             int exceptWidth = (int) (getPaddingLeft() + getPaddingRight()  
  61.                     + mRadius * 2 + paintWidth);  
  62.             widthMeasureSpec = MeasureSpec.makeMeasureSpec(exceptWidth,  
  63.                     MeasureSpec.EXACTLY);  
  64.         }  
  65.   
  66.         super.onMeasure(heightMeasureSpec, heightMeasureSpec);  
  67.   
  68.     }  
  69.   
  70.     @Override  
  71.     protected synchronized void onDraw(Canvas canvas) {  
  72.   
  73.         String text = getProgress() + "%";  
  74.         // mPaint.getTextBounds(text, 0, text.length(), mTextBound);  
  75.         float textWidth = mPaint.measureText(text);  
  76.         float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;  
  77.   
  78.         canvas.save();  
  79.         canvas.translate(getPaddingLeft(), getPaddingTop());  
  80.         mPaint.setStyle(Style.STROKE);  
  81.         // draw unreaded bar  
  82.         mPaint.setColor(mUnReachedBarColor);  
  83.         mPaint.setStrokeWidth(mUnReachedProgressBarHeight);  
  84.         canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);  
  85.         // draw reached bar  
  86.         mPaint.setColor(mReachedBarColor);  
  87.         mPaint.setStrokeWidth(mReachedProgressBarHeight);  
  88.         float sweepAngle = getProgress() * 1.0f / getMax() * 360;  
  89.         canvas.drawArc(new RectF(00, mRadius * 2, mRadius * 2), 0,  
  90.                 sweepAngle, false, mPaint);  
  91.         // draw text  
  92.         mPaint.setStyle(Style.FILL);  
  93.         canvas.drawText(text, mRadius - textWidth / 2, mRadius - textHeight,  
  94.                 mPaint);  
  95.   
  96.         canvas.restore();  
  97.   
  98.     }  
  99.   
  100. }  

首先获取它的专有属性mRadius,然后根据此属性去测量,测量完成绘制;

绘制的过程呢?

先绘制一个细一点的圆,然后绘制一个粗一点的弧度,二者叠在一起就行。文本呢,绘制在中间~~~总体,没什么代码量。

好了,两个进度条就到这了,是不是发现简单很多。总体设计上,存在些问题,如果抽取一个BaseProgressBar用于获取公共的属性;然后不同样子的进度条继承分别实现自己的测量和样子,这样结构可能会清晰些~~~


4、使用

布局文件

[html]  view plain  copy
 
  1. <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     xmlns:zhy="http://schemas.android.com/apk/res-auto"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent" >  
  6.   
  7.     <LinearLayout  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="match_parent"  
  10.         android:orientation="vertical"  
  11.         android:padding="25dp" >  
  12.   
  13.         <com.zhy.view.HorizontalProgressBarWithNumber  
  14.             android:id="@+id/id_progressbar01"  
  15.             android:layout_width="match_parent"  
  16.             android:layout_height="wrap_content"  
  17.             android:layout_marginTop="50dip"  
  18.             android:padding="5dp" />  
  19.   
  20.          
  21.   
  22.         <com.zhy.view.HorizontalProgressBarWithNumber  
  23.             android:layout_width="match_parent"  
  24.             android:layout_height="wrap_content"  
  25.             android:layout_marginTop="50dip"  
  26.             android:padding="5dp"  
  27.             android:progress="50"  
  28.             zhy:progress_text_color="#ffF53B03"  
  29.             zhy:progress_unreached_color="#ffF7C6B7" />  
  30.   
  31.         <com.zhy.view.RoundProgressBarWidthNumber  
  32.             android:id="@+id/id_progress02"  
  33.             android:layout_width="match_parent"  
  34.             android:layout_height="wrap_content"  
  35.             android:layout_marginTop="50dip"  
  36.             android:padding="5dp"  
  37.             android:progress="30" />  
  38.   
  39.         <com.zhy.view.RoundProgressBarWidthNumber  
  40.             android:layout_width="match_parent"  
  41.             android:layout_height="wrap_content"  
  42.             android:layout_marginTop="50dip"  
  43.             android:padding="5dp"  
  44.             android:progress="50"  
  45.             zhy:progress_reached_bar_height="20dp"  
  46.             zhy:progress_text_color="#ffF53B03"  
  47.             zhy:radius="60dp" />  
  48.        
  49.     </LinearLayout>  
  50.   
  51. </ScrollView>  

MainActivity

[java]  view plain  copy
 
  1. package com.zhy.sample.progressbar;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.os.Handler;  
  6.   
  7. import com.zhy.annotation.Log;  
  8. import com.zhy.view.HorizontalProgressBarWithNumber;  
  9.   
  10. public class MainActivity extends Activity {  
  11.   
  12.     private HorizontalProgressBarWithNumber mProgressBar;  
  13.     private static final int MSG_PROGRESS_UPDATE = 0x110;  
  14.   
  15.     private Handler mHandler = new Handler() {  
  16.         @Log  
  17.         public void handleMessage(android.os.Message msg) {  
  18.             int progress = mProgressBar.getProgress();  
  19.             mProgressBar.setProgress(++progress);  
  20.             if (progress >= 100) {  
  21.                 mHandler.removeMessages(MSG_PROGRESS_UPDATE);  
  22.                   
  23.             }  
  24.             mHandler.sendEmptyMessageDelayed(MSG_PROGRESS_UPDATE, 100);  
  25.         };  
  26.     };  
  27.   
  28.     @Log  
  29.     @Override  
  30.     protected void onCreate(Bundle savedInstanceState) {  
  31.         super.onCreate(savedInstanceState);  
  32.         setContentView(R.layout.activity_main);  
  33.         mProgressBar = (HorizontalProgressBarWithNumber) findViewById(R.id.id_progressbar01);  
  34.         mHandler.sendEmptyMessage(MSG_PROGRESS_UPDATE);  
  35.   
  36.     }  
  37.   
  38. }  

最后,本篇博客的目的呢?就是为了说下,类似ProgressBar这样的控件,如果你只是想去改变显示的样子,完全没必要从0去创建,复写onDraw即可,当然是个人观点,提出供大家参考。


源码点击下载  

你可能感兴趣的:(ProgressBar)