Android 自定義進度條
效果
國際慣例,效果圖奉上

在這裡插入圖片描述
目錄

在這裡插入圖片描述
前言
寫在前面,由於之前其實已經寫了部分自定義View的方法,所以本來應該按照之前的系列,來進行下載暫停動畫進度條,但是我把之前的圓形進度條和開始暫停動畫效果合併後,出現了一點小問題,讓我發現之前寫的自定義 View
,沒有使我真正的瞭解自定義 View
,那麼我覺得還是有很大的問題;那麼之後依舊會努力的寫自定義View,初步先寫靜態的自定義 View
,之後,加上動畫的自定義 VIew
,後續在加上相應的觸控事件,努力的把自定義 VIew
的方法方式全部給記錄下來;努力的融匯貫通。
正文
HorizontalProgressBarWithNumber
橫向的進度條;裡面有詳細的註釋
-
首先繼承自
ProgressBar
,這個是對基礎的ProgressBar
進行進一步的自定義,public class HorizontalProgressBarWithNumber extends ProgressBar{ ****** }
-
當我們想要自定義
VIew
的時候,先要構思效果,那麼就要設定各種屬性,如下
/** * 設定各種預設值 */ 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 未完成的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除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); 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(); }
- 上面當中的引數和屬性都在
attr.xml
中宣告的,如下
<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>
-
接下來,我們要重寫
onMeasure
方法,在其中獲取父控制元件傳遞過來的引數protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = measureHeight(heightMeasureSpec);//高度 setMeasuredDimension(width, height);//必須呼叫該方法來儲存View經過測量的到的寬度和高度 mRealWidth = getMeasuredWidth() - getPaddingRight() - getPaddingLeft();//真正的寬度值是減去左右padding }
-
其中
measureHeight()
方法是獲取檢視的高度,檢視的樣式中擁有進度條和文字,要判斷文字的高度和進度條的高度;
/** *EXACTLY:父控制元件告訴我們子控制元件了一個確定的大小,你就按這個大小來佈局。比如我們指定了確定的dp值和macth_parent的情況。 *AT_MOST:當前控制元件不能超過一個固定的最大值,一般是wrap_content的情況。 *UNSPECIFIED:當前控制元件沒有限制,要多大就有多大,這種情況很少出現。 * @param measureSpec * @return檢視的高度 */ private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec);//父佈局告訴我們控制元件的型別 int specSize = MeasureSpec.getSize(measureSpec);//父佈局傳過來的檢視大小 if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else { /** * mPaint.descent() 最高點的高度 * mPaint.ascent() 最低點的高度 */ float textHeight = (mPaint.descent() - mPaint.ascent());// 設定文字的高度 /** * Math.abs() 返回絕對值 *Math.max 返回最大值 *Math.min 返回最小值 */ result = (int) (getPaddingTop() + getPaddingBottom() + Math.max( Math.max(mReachedProgressBarHeight, mUnReachedProgressBarHeight), Math.abs(textHeight))); if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } return result; }
- 以上全部完成後,就可以開始
onDraw()
方法了;
/** * 開始畫 */ @Override protected synchronized void onDraw(Canvas canvas) { canvas.save(); /** * 設定偏移後的座標原點 以原來為基礎上偏移後, 例如: (100,100), translate(1,1), 座標原點(101,101); */ canvas.translate(getPaddingLeft(), getHeight() / 2); boolean noNeedBg = false; float radio = getProgress() * 1.0f / getMax();//設定進度 float progressPosX = (int) (mRealWidth * radio);//設定當前進度的寬度 String text = getProgress() + "%";//設定文字 //mPaint.getTextBounds(text, 0, text.length(), mTextBound); float textWidth = mPaint.measureText(text);//返回文字的寬度 float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;//設定文字的高度 if (progressPosX + textWidth > mRealWidth) {//當文字和當前進度的寬度大於bar的寬度時 progressPosX = mRealWidth - textWidth; noNeedBg = true; } // draw reached bar畫出bar float endX = progressPosX - mTextOffset / 2;//繪製已到達的進度 if (endX > 0) {//繪製已到達的進度 mPaint.setColor(mReachedBarColor); mPaint.setStrokeWidth(mReachedProgressBarHeight); canvas.drawLine(0, 0, endX, 0, mPaint); } // draw progress bar // measure text bound if (mIfDrawText) {//繪製文字 mPaint.setColor(mTextColor); canvas.drawText(text, progressPosX, -textHeight, mPaint); } // draw unreached bar if (!noNeedBg) {//繪製未到達的進度條 float start = progressPosX + mTextOffset / 2 + textWidth; mPaint.setColor(mUnReachedBarColor); mPaint.setStrokeWidth(mUnReachedProgressBarHeight); canvas.drawLine(start, 0, mRealWidth, 0, mPaint); } canvas.restore(); }
- 在分享兩個轉換的方法
/** * 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
全部實現,奉上全部程式碼
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 未完成的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除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); obtainStyledAttributes(attrs);//初始化引數 mPaint.setTextSize(mTextSize);//文字大小 mPaint.setColor(mTextColor);//文字顏色 } @Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = measureHeight(heightMeasureSpec);//高度 setMeasuredDimension(width, height);//必須呼叫該方法來儲存View經過測量的到的寬度和高度 mRealWidth = getMeasuredWidth() - getPaddingRight() - getPaddingLeft();//真正的寬度值是減去左右padding } /** *EXACTLY:父控制元件告訴我們子控制元件了一個確定的大小,你就按這個大小來佈局。比如我們指定了確定的dp值和macth_parent的情況。 *AT_MOST:當前控制元件不能超過一個固定的最大值,一般是wrap_content的情況。 *UNSPECIFIED:當前控制元件沒有限制,要多大就有多大,這種情況很少出現。 * @param measureSpec * @return檢視的高度 */ private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec);//父佈局告訴我們控制元件的型別 int specSize = MeasureSpec.getSize(measureSpec);//父佈局傳過來的檢視大小 if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else { /** * mPaint.descent() 最高點的高度 * mPaint.ascent() 最低點的高度 */ float textHeight = (mPaint.descent() - mPaint.ascent());// 設定文字的高度 /** * Math.abs() 返回絕對值 *Math.max 返回最大值 *Math.min 返回最小值 */ result = (int) (getPaddingTop() + getPaddingBottom() + Math.max( Math.max(mReachedProgressBarHeight, mUnReachedProgressBarHeight), Math.abs(textHeight))); if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } return result; } /** * 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(); } /** * 開始畫 */ @Override protected synchronized void onDraw(Canvas canvas) { canvas.save(); /** * 設定偏移後的座標原點 以原來為基礎上偏移後, 例如: (100,100), translate(1,1), 座標原點(101,101); */ canvas.translate(getPaddingLeft(), getHeight() / 2); boolean noNeedBg = false; float radio = getProgress() * 1.0f / getMax();//設定進度 float progressPosX = (int) (mRealWidth * radio);//設定當前進度的寬度 String text = getProgress() + "%";//設定文字 //mPaint.getTextBounds(text, 0, text.length(), mTextBound); float textWidth = mPaint.measureText(text);//返回文字的寬度 float textHeight = (mPaint.descent() + mPaint.ascent()) / 2;//設定文字的高度 if (progressPosX + textWidth > mRealWidth) {//當文字和當前進度的寬度大於bar的寬度時 progressPosX = mRealWidth - textWidth; noNeedBg = true; } // draw reached bar畫出bar float endX = progressPosX - mTextOffset / 2;//設定文字補償寬度 if (endX > 0) { mPaint.setColor(mReachedBarColor); mPaint.setStrokeWidth(mReachedProgressBarHeight); canvas.drawLine(0, 0, endX, 0, mPaint); } // draw progress bar // measure text bound if (mIfDrawText) { mPaint.setColor(mTextColor); canvas.drawText(text, progressPosX, -textHeight, mPaint); } // draw unreached bar if (!noNeedBg) { float start = progressPosX + mTextOffset / 2 + textWidth; mPaint.setColor(mUnReachedBarColor); mPaint.setStrokeWidth(mUnReachedProgressBarHeight); canvas.drawLine(start, 0, mRealWidth, 0, mPaint); } canvas.restore(); } /** * 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()); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mRealWidth = w - getPaddingRight() - getPaddingLeft(); } }
實現
-
xml
介面
<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" > <com.zhy.view.HorizontalProgressBarWithNumber android:id="@+id/id_progressbar01" android:layout_width="300dp" android:layout_height="wrap_content" android:layout_marginTop="50dip" android:padding="5dp" /> </ScrollView>
-
mainActivity()
實現
public class MainActivity extends Activity { private HorizontalProgressBarWithNumber mProgressBar; private Handler mHandler = new Handler() { 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); }; }; @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); }
- 以上就是全部功能的實現了
總結
自定義 View
View
感謝
這個是從鴻陽大神的Github上找到的例子,嗯,值得學習,感謝鴻陽大神;