一、概述
最近需要用進度條,秉著不重復造輪子的原則,上github上搜索了一番,看了幾個覺得比較好看的ProgressBar,比如:daimajia的等。簡單看了下代碼,基本都是繼承自View,徹徹底底的自定義了一個進度條。盯著那絢麗滾動條,忽然覺得,為什么要通過View去寫一個滾動條,系統已經提供了ProgressBar以及屬于它的特性,我們沒必要重新去構建一個,但是系統的又比較丑,不同版本變現還不一定一樣。那么得出我們的目標:改變系統ProgressBar的樣子。 
對沒錯,我們沒有必要去從0打造一個ProgressBar,人家雖然長的不好看,但是特性以及穩定性還是剛剛的,我們只需要為其整下容就ok了。 
接下來,我們貼下效果圖:
1、橫向的進度條

2、圓形的進度條

沒錯,這就是我們的進度條效果,橫向的模仿了daimajia的進度條樣子。不過我們繼承子ProgressBar,簡單的為其整個容,代碼清晰易懂 。為什么說,易懂呢?
橫向那個進度條,大家會drawLine()和drawText()吧,那么通過getWidth()拿到控件的寬度,再通過getProgress()拿到進度,按比例控制繪制線的長短,字的位置還不是分分鐘的事。
二、實現
 橫向的滾動條繪制肯定需要一些屬性,比如已/未到達進度的顏色、寬度,文本的顏色、大小等。 
 本來呢,我是想通過系統ProgressBar的progressDrawable,從里面提取一些屬性完成繪制需要的參數的。但是,最終呢,反而讓代碼變得復雜。所以最終還是改用自定義屬性。 說道自定義屬性,大家應該已經不陌生了。
1、HorizontalProgressBarWithNumber
(1)自定義屬性
values/attr_progress_bar.xml:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="HorizontalProgressBarWithNumber"> <attr name="progress_unreached_color" format="color" /> <attr name="progress_reached_color" format="color" /> <attr name="progress_reached_bar_height" format="dimension" /> <attr name="progress_unreached_bar_height" format="dimension" /> <attr name="progress_text_size" format="dimension" /> <attr name="progress_text_color" format="color" /> <attr name="progress_text_offset" format="dimension" /> <attr name="progress_text_visibility" format="enum"> <enum name="visible" value="0" /> <enum name="invisible" value="1" /> </attr> </declare-styleable> <declare-styleable name="RoundProgressBarWidthNumber"> <attr name="radius" format="dimension" /> </declare-styleable> </resources>
(2)構造中獲取
public class HorizontalProgressBarWithNumber extends ProgressBar {    private static final int DEFAULT_TEXT_SIZE = 10;   private static final int DEFAULT_TEXT_COLOR = 0XFFFC00D1;   private static final int DEFAULT_COLOR_UNREACHED_COLOR = 0xFFd3d6da;   private static final int DEFAULT_HEIGHT_REACHED_PROGRESS_BAR = 2;   private static final int DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR = 2;   private static final int DEFAULT_SIZE_TEXT_OFFSET = 10;    /**    * painter of all drawing things    */   protected Paint mPaint = new Paint();   /**    * color of progress number    */   protected int mTextColor = DEFAULT_TEXT_COLOR;   /**    * size of text (sp)    */   protected int mTextSize = sp2px(DEFAULT_TEXT_SIZE);    /**    * offset of draw progress    */   protected int mTextOffset = dp2px(DEFAULT_SIZE_TEXT_OFFSET);    /**    * height of reached progress bar    */   protected int mReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_REACHED_PROGRESS_BAR);    /**    * color of reached bar    */   protected int mReachedBarColor = DEFAULT_TEXT_COLOR;   /**    * color of unreached bar    */   protected int mUnReachedBarColor = DEFAULT_COLOR_UNREACHED_COLOR;   /**    * height of unreached progress bar    */   protected int mUnReachedProgressBarHeight = dp2px(DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR);   /**    * view width except padding    */   protected int mRealWidth;      protected boolean mIfDrawText = true;    protected static final int VISIBLE = 0;    public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs)   {     this(context, attrs, 0);   }    public HorizontalProgressBarWithNumber(Context context, AttributeSet attrs,       int defStyle)   {     super(context, attrs, defStyle);          setHorizontalScrollBarEnabled(true);      obtainStyledAttributes(attrs);      mPaint.setTextSize(mTextSize);     mPaint.setColor(mTextColor);    }      /**    * get the styled attributes    *    * @param attrs    */   private void obtainStyledAttributes(AttributeSet attrs)   {     // init values from custom attributes     final TypedArray attributes = getContext().obtainStyledAttributes(         attrs, R.styleable.HorizontalProgressBarWithNumber);      mTextColor = attributes         .getColor(             R.styleable.HorizontalProgressBarWithNumber_progress_text_color,             DEFAULT_TEXT_COLOR);     mTextSize = (int) attributes.getDimension(         R.styleable.HorizontalProgressBarWithNumber_progress_text_size,         mTextSize);      mReachedBarColor = attributes         .getColor(             R.styleable.HorizontalProgressBarWithNumber_progress_reached_color,             mTextColor);     mUnReachedBarColor = attributes         .getColor(             R.styleable.HorizontalProgressBarWithNumber_progress_unreached_color,             DEFAULT_COLOR_UNREACHED_COLOR);     mReachedProgressBarHeight = (int) attributes         .getDimension(             R.styleable.HorizontalProgressBarWithNumber_progress_reached_bar_height,             mReachedProgressBarHeight);     mUnReachedProgressBarHeight = (int) attributes         .getDimension(             R.styleable.HorizontalProgressBarWithNumber_progress_unreached_bar_height,             mUnReachedProgressBarHeight);     mTextOffset = (int) attributes         .getDimension(             R.styleable.HorizontalProgressBarWithNumber_progress_text_offset,             mTextOffset);      int textVisible = attributes         .getInt(R.styleable.HorizontalProgressBarWithNumber_progress_text_visibility,             VISIBLE);     if (textVisible != VISIBLE)     {       mIfDrawText = false;     }     attributes.recycle();   } 嗯,看起來代碼挺長,其實都是在獲取自定義屬性,沒什么技術含量。
(3)onMeasure
剛才不是出onDraw里面寫寫就行了么,為什么要改onMeasure呢,主要是因為我們所有的屬性比如進度條寬度讓用戶自定義了,所以我們的測量也得稍微變下。
@Override protected synchronized void onMeasure(int widthMeasureSpec,     int heightMeasureSpec) {   int heightMode = MeasureSpec.getMode(heightMeasureSpec);    if (heightMode != MeasureSpec.EXACTLY)   {      float textHeight = (mPaint.descent() + mPaint.ascent());     int exceptHeight = (int) (getPaddingTop() + getPaddingBottom() + Math         .max(Math.max(mReachedProgressBarHeight,             mUnReachedProgressBarHeight), Math.abs(textHeight)));      heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight,         MeasureSpec.EXACTLY);   }   super.onMeasure(widthMeasureSpec, heightMeasureSpec);  } 寬度我們不變,所以的自定義屬性不涉及寬度,高度呢,只考慮不是EXACTLY的情況(用戶明確指定了,我們就不管了),根據padding和進度條寬度算出自己想要的,如果非EXACTLY下,我們進行exceptHeight封裝,傳入給控件進行測量高度。
測量完,就到我們的onDraw了~~~
(4)onDraw
@Override   protected synchronized void onDraw(Canvas canvas)   {     canvas.save();     //畫筆平移到指定paddingLeft, getHeight() / 2位置,注意以后坐標都為以此為0,0     canvas.translate(getPaddingLeft(), getHeight() / 2);      boolean noNeedBg = false;     //當前進度和總值的比例     float radio = getProgress() * 1.0f / getMax();     //已到達的寬度     float progressPosX = (int) (mRealWidth * radio);     //繪制的文本     String text = getProgress() + "%";      //拿到字體的寬度和高度     float textWidth = mPaint.measureText(text);     float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;      //如果到達最后,則未到達的進度條不需要繪制     if (progressPosX + textWidth > mRealWidth)     {       progressPosX = mRealWidth - textWidth;       noNeedBg = true;     }      // 繪制已到達的進度     float endX = progressPosX - mTextOffset / 2;     if (endX > 0)     {       mPaint.setColor(mReachedBarColor);       mPaint.setStrokeWidth(mReachedProgressBarHeight);       canvas.drawLine(0, 0, endX, 0, mPaint);     }        // 繪制文本     if (mIfDrawText)     {       mPaint.setColor(mTextColor);       canvas.drawText(text, progressPosX, -textHeight, mPaint);     }      // 繪制未到達的進度條     if (!noNeedBg)     {       float start = progressPosX + mTextOffset / 2 + textWidth;       mPaint.setColor(mUnReachedBarColor);       mPaint.setStrokeWidth(mUnReachedProgressBarHeight);       canvas.drawLine(start, 0, mRealWidth, 0, mPaint);     }      canvas.restore();    }    @Override   protected void onSizeChanged(int w, int h, int oldw, int oldh)   {     super.onSizeChanged(w, h, oldw, oldh);     mRealWidth = w - getPaddingRight() - getPaddingLeft();    } 其實核心方法就是onDraw了,但是呢,onDraw也很簡單,繪制線、繪制文本、繪制線,結束。
還有兩個簡單的輔助方法:
/**  * dp 2 px  *  * @param dpVal  */ protected int dp2px(int dpVal) {   return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,       dpVal, getResources().getDisplayMetrics()); }  /**  * sp 2 px  *  * @param spVal  * @return  */ protected int sp2px(int spVal) {   return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,       spVal, getResources().getDisplayMetrics());  } 好了,到此我們的橫向進度就結束了,是不是很簡單~~如果你是自定義View,你還得考慮progress的更新,考慮狀態的銷毀與恢復等等復雜的東西。
接下來看我們的RoundProgressBarWidthNumber圓形的進度條。
2、RoundProgressBarWidthNumber   
圓形的進度條和橫向的進度條基本變量都是一致的,于是我就讓RoundProgressBarWidthNumber extends HorizontalProgressBarWithNumber 了。
然后需要改變的就是測量和onDraw了:
完整代碼:
package com.zhy.view;  import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint.Cap; import android.graphics.Paint.Style; import android.graphics.RectF; import android.util.AttributeSet;  import com.zhy.library.view.R;  public class RoundProgressBarWidthNumber extends     HorizontalProgressBarWithNumber {   /**    * mRadius of view    */   private int mRadius = dp2px(30);    public RoundProgressBarWidthNumber(Context context) {     this(context, null);   }    public RoundProgressBarWidthNumber(Context context, AttributeSet attrs) {     super(context, attrs);      mReachedProgressBarHeight = (int) (mUnReachedProgressBarHeight * 2.5f);     TypedArray ta = context.obtainStyledAttributes(attrs,         R.styleable.RoundProgressBarWidthNumber);     mRadius = (int) ta.getDimension(         R.styleable.RoundProgressBarWidthNumber_radius, mRadius);     ta.recycle();      mTextSize = sp2px(14);      mPaint.setStyle(Style.STROKE);     mPaint.setAntiAlias(true);     mPaint.setDither(true);     mPaint.setStrokeCap(Cap.ROUND);    }    @Override   protected synchronized void onMeasure(int widthMeasureSpec,       int heightMeasureSpec) {     int heightMode = MeasureSpec.getMode(heightMeasureSpec);     int widthMode = MeasureSpec.getMode(widthMeasureSpec);      int paintWidth = Math.max(mReachedProgressBarHeight,         mUnReachedProgressBarHeight);      if (heightMode != MeasureSpec.EXACTLY) {        int exceptHeight = (int) (getPaddingTop() + getPaddingBottom()           + mRadius * 2 + paintWidth);       heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight,           MeasureSpec.EXACTLY);     }     if (widthMode != MeasureSpec.EXACTLY) {       int exceptWidth = (int) (getPaddingLeft() + getPaddingRight()           + mRadius * 2 + paintWidth);       widthMeasureSpec = MeasureSpec.makeMeasureSpec(exceptWidth,           MeasureSpec.EXACTLY);     }      super.onMeasure(heightMeasureSpec, heightMeasureSpec);    }    @Override   protected synchronized void onDraw(Canvas canvas) {      String text = getProgress() + "%";     // mPaint.getTextBounds(text, 0, text.length(), mTextBound);     float textWidth = mPaint.measureText(text);     float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;      canvas.save();     canvas.translate(getPaddingLeft(), getPaddingTop());     mPaint.setStyle(Style.STROKE);     // draw unreaded bar     mPaint.setColor(mUnReachedBarColor);     mPaint.setStrokeWidth(mUnReachedProgressBarHeight);     canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);     // draw reached bar     mPaint.setColor(mReachedBarColor);     mPaint.setStrokeWidth(mReachedProgressBarHeight);     float sweepAngle = getProgress() * 1.0f / getMax() * 360;     canvas.drawArc(new RectF(0, 0, mRadius * 2, mRadius * 2), 0,         sweepAngle, false, mPaint);     // draw text     mPaint.setStyle(Style.FILL);     canvas.drawText(text, mRadius - textWidth / 2, mRadius - textHeight,         mPaint);      canvas.restore();    }  } 首先獲取它的專有屬性mRadius,然后根據此屬性去測量,測量完成繪制;
繪制的過程呢?
先繪制一個細一點的圓,然后繪制一個粗一點的弧度,二者疊在一起就行。文本呢,繪制在中間~~~總體,沒什么代碼量。
好了,兩個進度條就到這了,是不是發現簡單很多。總體設計上,存在些問題,如果抽取一個BaseProgressBar用于獲取公共的屬性;然后不同樣子的進度條繼承分別實現自己的測量和樣子,這樣結構可能會清晰些~~~
三、使用
布局文件
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:zhy="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="25dp" > <com.zhy.view.HorizontalProgressBarWithNumber android:id="@+id/id_progressbar01" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dip" android:padding="5dp" /> <com.zhy.view.HorizontalProgressBarWithNumber android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dip" android:padding="5dp" android:progress="50" zhy:progress_text_color="#ffF53B03" zhy:progress_unreached_color="#ffF7C6B7" /> <com.zhy.view.RoundProgressBarWidthNumber android:id="@+id/id_progress02" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dip" android:padding="5dp" android:progress="30" /> <com.zhy.view.RoundProgressBarWidthNumber android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dip" android:padding="5dp" android:progress="50" zhy:progress_reached_bar_height="20dp" zhy:progress_text_color="#ffF53B03" zhy:radius="60dp" /> </LinearLayout> </ScrollView>
MainActivity
package com.zhy.sample.progressbar;  import android.app.Activity; import android.os.Bundle; import android.os.Handler;  import com.zhy.annotation.Log; import com.zhy.view.HorizontalProgressBarWithNumber;  public class MainActivity extends Activity {    private HorizontalProgressBarWithNumber mProgressBar;   private static final int MSG_PROGRESS_UPDATE = 0x110;    private Handler mHandler = new Handler() {     @Log     public void handleMessage(android.os.Message msg) {       int progress = mProgressBar.getProgress();       mProgressBar.setProgress(++progress);       if (progress >= 100) {         mHandler.removeMessages(MSG_PROGRESS_UPDATE);                }       mHandler.sendEmptyMessageDelayed(MSG_PROGRESS_UPDATE, 100);     };   };    @Log   @Override   protected void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.activity_main);     mProgressBar = (HorizontalProgressBarWithNumber) findViewById(R.id.id_progressbar01);     mHandler.sendEmptyMessage(MSG_PROGRESS_UPDATE);    }  } 新聞熱點
疑難解答