Android 打造形形色色的進度條 實現可以如此簡單
轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/43371299 ,本文出自:【張鴻洋的博客】
1、概述
最近需要用進度條,秉著不重復造輪子的原則,上github上搜索了一番,看了幾個覺得比較好看的ProgressBar,比如:daimajia的等。簡單看了下代碼,基本都是繼承自View,徹徹底底的自定義了一個進度條。盯著那絢麗滾動條,忽然覺得,為什么要通過View去寫一個滾動條,系統已經提供了ProgressBar以及屬于它的特性,我們沒必要重新去構建一個,但是系統的又比較丑,不同版本變現還不一定一樣。那么得出我們的目標:改變系統ProgressBar的樣子。
??對沒錯,我們沒有必要去從0打造一個ProgressBar,人家雖然長的不好看,但是特性以及穩定性還是剛剛的,我們只需要為其整下容就ok了。
說到整容,大家都知道我們的控件是通過onDraw()畫出來的,那么我們只需要去覆蓋它的onDraw()方法,自己寫下就ok 。
對了,我創建了一個微信公眾號,歡迎關注,左邊欄目上掃一掃即可。
??接下來,我們貼下效果圖:
2、效果圖
1、橫向的進度條

2、圓形的進度條

沒錯,這就是我們的進度條效果,橫向的模仿了daimajia的進度條樣子。不過我們繼承子ProgressBar,簡單的為其整個容,代碼清晰易懂 。為什么說,易懂呢?
橫向那個進度條,大家會drawLine()和drawText()吧,那么通過getWidth()拿到控件的寬度,再通過getProgress()拿到進度,按比例控制繪制線的長短,字的位置還不是分分鐘的事。
github源碼地址:Android-ProgressBarWidthNumber歡迎大家star or fork 。
3、實現
橫向的滾動條繪制肯定需要一些屬性,比如已/未到達進度的顏色、寬度,文本的顏色、大小等。
本來呢,我是想通過系統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用于獲取公共的屬性;然后不同樣子的進度條繼承分別實現自己的測量和樣子,這樣結構可能會清晰些~~~
4、使用
布局文件
<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);
}
} 最后,本篇博客的目的呢?就是為了說下,類似ProgressBar這樣的控件,如果你只是想去改變顯示的樣子,完全沒必要從0去創建,復寫onDraw即可,當然是個人觀點,提出供大家參考。
群號:423372824
博主部分視頻已經上線,如果你不喜歡枯燥的文本,請猛戳(初錄,期待您的支持):
來自: http://blog.csdn.net//lmj623565791/article/details/43371299