1、概述

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

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

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

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

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

2、效果图

1、横向的进度条

Android 打造形形色色的进度条 实现可以如此简单_自定义属性


2、圆形的进度条

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

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

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

3、实现

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

1、HorizontalProgressBarWithNumber

1、自定义属性

values/attr_progress_bar.xml:

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、构造中获取

    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. 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. 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. int) attributes  
    102.                 .getDimension(  
    103.                         R.styleable.HorizontalProgressBarWithNumber_progress_reached_bar_height,  
    104.                         mReachedProgressBarHeight);  
    105. int) attributes  
    106.                 .getDimension(  
    107.                         R.styleable.HorizontalProgressBarWithNumber_progress_unreached_bar_height,  
    108.                         mUnReachedProgressBarHeight);  
    109. 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. 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

      1. @Override  
      2. protected synchronized void onDraw(Canvas canvas)  
      3.     {  
      4.         canvas.save();  
      5. //画笔平移到指定paddingLeft, getHeight() / 2位置,注意以后坐标都为以此为0,0  
      6. 2);  
      7.   
      8. boolean noNeedBg = false;  
      9. //当前进度和总值的比例  
      10. float radio = getProgress() * 1.0f / getMax();  
      11. //已到达的宽度  
      12. float progressPosX = (int) (mRealWidth * radio);  
      13. //绘制的文本  
      14. "%";  
      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. true;  
      25.         }  
      26.   
      27. // 绘制已到达的进度  
      28. float endX = progressPosX - mTextOffset / 2;  
      29. if (endX > 0)  
      30.         {  
      31.             mPaint.setColor(mReachedBarColor);  
      32.             mPaint.setStrokeWidth(mReachedProgressBarHeight);  
      33. 0, 0, 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. 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也很简单,绘制线、绘制文本、绘制线,结束。

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

      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了:

      完整代码:

      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. int) (mUnReachedProgressBarHeight * 2.5f);  
      28.         TypedArray ta = context.obtainStyledAttributes(attrs,  
      29.                 R.styleable.RoundProgressBarWidthNumber);  
      30. int) ta.getDimension(  
      31.                 R.styleable.RoundProgressBarWidthNumber_radius, mRadius);  
      32.         ta.recycle();  
      33.   
      34. 14);  
      35.   
      36.         mPaint.setStyle(Style.STROKE);  
      37. true);  
      38. 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. 2 + paintWidth);  
      56.             heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight,  
      57.                     MeasureSpec.EXACTLY);  
      58.         }  
      59. if (widthMode != MeasureSpec.EXACTLY) {  
      60. int exceptWidth = (int) (getPaddingLeft() + getPaddingRight()  
      61. 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. "%";  
      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. new RectF(0, 0, mRadius * 2, mRadius * 2), 0,  
      90. false, mPaint);  
      91. // draw text  
      92.         mPaint.setStyle(Style.FILL);  
      93. 2, mRadius - textHeight,  
      94.                 mPaint);  
      95.   
      96.         canvas.restore();  
      97.   
      98.     }  
      99.   
      100. }


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

      绘制的过程呢?

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

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

      4、使用

      布局文件

      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

      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. 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即可,当然是个人观点,提出供大家参考。