NumberProgressBar:一个简约性感的数字ProgressBar
来自: http://www.jcodecraeer.com//a/anzhuokaifa/androidkaifa/2014/0813/1645.html
原生的ProgressBar在不同的主题下风格迥异,有矩形条状的,有代表加载进行中的圆圈风格的,在4.0的holo风格下这些ProgressBar都还比较好看,但是在非holo风格下让人厌烦。我不排斥使用原生控件,但是有时我们的app可能比较个性化,需要更有个性的ProgressBar。
下面这款开源ProgressBar就比较有个性。
NumberProgressBar
github地址 https://github.com/daimajia/NumberProgressBar
该控件虽然也叫ProgressBar,但是和sdk中的ProgressBar控件没有任何继承关系,直接继承子view。控件分为三部分,如图:
用法:
xml中
<com.daimajia.numberprogressbar.NumberProgressBar android:id="@+id/number_progress_bar" style="@style/NumberProgressBar_Default" />
除了默认的style,你还可以设置成如下style:
NumberProgressBar_Default
NumberProgressBar_Passing_Green
NumberProgressBar_Relax_Blue
NumberProgressBar_Grace_Yellow
NumberProgressBar_Warning_Red
NumberProgressBar_Funny_Orange
NumberProgressBar_Beauty_Red
NumberProgressBar_Twinkle_Night
对应的外观如下:
除了直接用已经定义好的style,你还可以通过设置属性来改变外观。
NumberProgressBar的一些属性:
reached area和unreached area
-
颜色
-
高度
文字部分,描述进程的百分比数字
-
颜色
-
字体大小
-
是否可见
-
和reached area与unreached area之间的距离
整个bar
-
最大进度 max progress
-
当前进度 current progress
比如默认情况下
<com.daimajia.numberprogressbar.NumberProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" custom:progress_unreached_color="#CCCCCC" custom:progress_reached_color="#3498DB" custom:progress_unreached_bar_height="0.75dp" custom:progress_reached_bar_height="1.5dp" custom:progress_text_size="10sp" custom:progress_text_color="#3498DB" custom:progress_text_offset="1dp" custom:progress_text_visibility="visible" custom:max="100" custom:progress="80" />
能体现进度的动画效果:
如上面的第一张图所示,github项目中的demo实现了NumberProgressBar的动画效果,但是这个动画效果并不是NumberProgressBar自带的,而是在外部通过不断调用setProgress做到的,demo中
final NumberProgressBar bnp = (NumberProgressBar)findViewById(R.id.numberbar1); counter = 0; timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { runOnUiThread(new Runnable() { @Override public void run() { bnp.incrementProgressBy(1); counter ++; if (counter == 110) { bnp.setProgress(0); counter=0; } } }); } }, 1000, 100);
incrementProgressBy表示Progress在现有基础上增加多少,从上面的代码可以看出,demo中NumberProgressBar的动态增加是采用timer实现的,也可以用属性动画来实现,当然在现实运用中可能是一些跟网络有关的事件来实现。
NumberProgressBar的源码实现:
如果你已经学会了如何自定义一个view,那么NumberProgressBar的代码是很容易看懂的。这里想指出的是有个细节我个人持保留意见,那就是作者在onMeasure方法中调用了自己实现的measure方法,在View的绘制中是先measure然后再在measure方法中调用onMeasure,而继承子view的子类一般只重写onMeasure方法。虽然这里并不会引起什么错误,但是我觉得还是遵守view的绘制流程比较好。
最后贴出NumberProgressBar的java部分的代码,代码不多:
package com.daimajia.numberprogressbar; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; import android.view.View; /** * Created by daimajia on 14-4-30. */ public class NumberProgressBar extends View { private Context mContext; /** * The max progress, default is 100 */ private int mMax = 100; /** * current progress, can not exceed the max progress. */ private int mProgress = 0; /** * the progress area bar color */ private int mReachedBarColor; /** * the bar unreached area color. */ private int mUnreachedBarColor; /** * the progress text color. */ private int mTextColor; /** * the progress text size */ private float mTextSize; /** * the height of the reached area */ private float mReachedBarHeight; /** * the height of the unreached area */ private float mUnreachedBarHeight; private final int default_text_color = Color.rgb(66, 145, 241); private final int default_reached_color = Color.rgb(66,145,241); private final int default_unreached_color = Color.rgb(204, 204, 204); private final float default_progress_text_offset; private final float default_text_size; private final float default_reached_bar_height; private final float default_unreached_bar_height; /** * for save and restore instance of progressbar. */ private static final String INSTANCE_STATE = "saved_instance"; private static final String INSTANCE_TEXT_COLOR = "text_color"; private static final String INSTANCE_TEXT_SIZE = "text_size"; private static final String INSTANCE_REACHED_BAR_HEIGHT = "reached_bar_height"; private static final String INSTANCE_REACHED_BAR_COLOR = "reached_bar_color"; private static final String INSTANCE_UNREACHED_BAR_HEIGHT = "unreached_bar_height"; private static final String INSTANCE_UNREACHED_BAR_COLOR = "unreached_bar_color"; private static final String INSTANCE_MAX = "max"; private static final String INSTANCE_PROGRESS = "progress"; private static final int PROGRESS_TEXT_VISIBLE = 0; private static final int PROGRESS_TEXT_INVISIBLE = 1; /** * the width of the text that to be drawn */ private float mDrawTextWidth; /** * the drawn text start */ private float mDrawTextStart; /** *the drawn text end */ private float mDrawTextEnd; /** * the text that to be drawn in onDraw() */ private String mCurrentDrawText; /** * the Paint of the reached area. */ private Paint mReachedBarPaint; /** * the Painter of the unreached area. */ private Paint mUnreachedBarPaint; /** * the Painter of the progress text. */ private Paint mTextPaint; /** * Unreached Bar area to draw rect. */ private RectF mUnreachedRectF = new RectF(0,0,0,0); /** * reached bar area rect. */ private RectF mReachedRectF = new RectF(0,0,0,0); /** * the progress text offset. */ private float mOffset; /** * determine if need to draw unreached area */ private boolean mDrawUnreachedBar = true; private boolean mDrawReachedBar = true; private boolean mIfDrawText = true; public enum ProgressTextVisibility{ Visible,Invisible }; public NumberProgressBar(Context context) { this(context, null); } public NumberProgressBar(Context context, AttributeSet attrs) { this(context, attrs, R.attr.numberProgressBarStyle); } public NumberProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; default_reached_bar_height = dp2px(1.5f); default_unreached_bar_height = dp2px(1.0f); default_text_size = sp2px(10); default_progress_text_offset = dp2px(3.0f); //load styled attributes. final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.NumberProgressBar, defStyleAttr, 0); mReachedBarColor = attributes.getColor(R.styleable.NumberProgressBar_progress_reached_color, default_reached_color); mUnreachedBarColor = attributes.getColor(R.styleable.NumberProgressBar_progress_unreached_color,default_unreached_color); mTextColor = attributes.getColor(R.styleable.NumberProgressBar_progress_text_color,default_text_color); mTextSize = attributes.getDimension(R.styleable.NumberProgressBar_progress_text_size, default_text_size); mReachedBarHeight = attributes.getDimension(R.styleable.NumberProgressBar_progress_reached_bar_height,default_reached_bar_height); mUnreachedBarHeight = attributes.getDimension(R.styleable.NumberProgressBar_progress_unreached_bar_height,default_unreached_bar_height); mOffset = attributes.getDimension(R.styleable.NumberProgressBar_progress_text_offset,default_progress_text_offset); int textVisible = attributes.getInt(R.styleable.NumberProgressBar_progress_text_visibility,PROGRESS_TEXT_VISIBLE); if(textVisible != PROGRESS_TEXT_VISIBLE){ mIfDrawText = false; } setProgress(attributes.getInt(R.styleable.NumberProgressBar_progress,0)); setMax(attributes.getInt(R.styleable.NumberProgressBar_max, 100)); // attributes.recycle(); initializePainters(); } @Override protected int getSuggestedMinimumWidth() { return (int)mTextSize; } @Override protected int getSuggestedMinimumHeight() { return Math.max((int)mTextSize,Math.max((int)mReachedBarHeight,(int)mUnreachedBarHeight)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measure(widthMeasureSpec,true), measure(heightMeasureSpec,false)); } private int measure(int measureSpec,boolean isWidth){ int result; int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); int padding = isWidth?getPaddingLeft()+getPaddingRight():getPaddingTop()+getPaddingBottom(); if(mode == MeasureSpec.EXACTLY){ result = size; }else{ result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight(); result += padding; if(mode == MeasureSpec.AT_MOST){ if(isWidth) { result = Math.max(result, size); } else{ result = Math.min(result, size); } } } return result; } @Override protected void onDraw(Canvas canvas) { if(mIfDrawText){ calculateDrawRectF(); }else{ calculateDrawRectFWithoutProgressText(); } if(mDrawReachedBar){ canvas.drawRect(mReachedRectF,mReachedBarPaint); } if(mDrawUnreachedBar) { canvas.drawRect(mUnreachedRectF, mUnreachedBarPaint); } if(mIfDrawText) canvas.drawText(mCurrentDrawText,mDrawTextStart,mDrawTextEnd,mTextPaint); } private void initializePainters(){ mReachedBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mReachedBarPaint.setColor(mReachedBarColor); mUnreachedBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mUnreachedBarPaint.setColor(mUnreachedBarColor); mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTextPaint.setColor(mTextColor); mTextPaint.setTextSize(mTextSize); } private void calculateDrawRectFWithoutProgressText(){ mReachedRectF.left = getPaddingLeft(); mReachedRectF.top = getHeight()/2.0f - mReachedBarHeight / 2.0f; mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight() )/(getMax()*1.0f) * getProgress() + getPaddingLeft(); mReachedRectF.bottom = getHeight()/2.0f + mReachedBarHeight / 2.0f; mUnreachedRectF.left = mReachedRectF.right; mUnreachedRectF.right = getWidth() - getPaddingRight(); mUnreachedRectF.top = getHeight()/2.0f + - mUnreachedBarHeight / 2.0f; mUnreachedRectF.bottom = getHeight()/2.0f + mUnreachedBarHeight / 2.0f; } private void calculateDrawRectF(){ mCurrentDrawText = String.format("%d%%",getProgress()*100/getMax()); mDrawTextWidth = mTextPaint.measureText(mCurrentDrawText); if(getProgress() == 0){ mDrawReachedBar = false; mDrawTextStart = getPaddingLeft(); }else{ mDrawReachedBar = true; mReachedRectF.left = getPaddingLeft(); mReachedRectF.top = getHeight()/2.0f - mReachedBarHeight / 2.0f; mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight() )/(getMax()*1.0f) * getProgress() - mOffset + getPaddingLeft(); mReachedRectF.bottom = getHeight()/2.0f + mReachedBarHeight / 2.0f; mDrawTextStart = (mReachedRectF.right + mOffset); } mDrawTextEnd = (int) ((getHeight() / 2.0f) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f)) ; if((mDrawTextStart + mDrawTextWidth )>= getWidth() - getPaddingRight()){ mDrawTextStart = getWidth() - getPaddingRight() - mDrawTextWidth; mReachedRectF.right = mDrawTextStart - mOffset; } float unreachedBarStart = mDrawTextStart + mDrawTextWidth + mOffset; if(unreachedBarStart >= getWidth() - getPaddingRight()){ mDrawUnreachedBar = false; }else{ mDrawUnreachedBar = true; mUnreachedRectF.left = unreachedBarStart; mUnreachedRectF.right = getWidth() - getPaddingRight(); mUnreachedRectF.top = getHeight()/2.0f + - mUnreachedBarHeight / 2.0f; mUnreachedRectF.bottom = getHeight()/2.0f + mUnreachedBarHeight / 2.0f; } } /** * get progress text color * @return progress text color */ public int getTextColor() { return mTextColor; } /** * get progress text size * @return progress text size */ public float getProgressTextSize() { return mTextSize; } public int getUnreachedBarColor() { return mUnreachedBarColor; } public int getReachedBarColor() { return mReachedBarColor; } public int getProgress() { return mProgress; } public int getMax() { return mMax; } public float getReachedBarHeight(){ return mReachedBarHeight; } public float getUnreachedBarHeight(){ return mUnreachedBarHeight; } public void setProgressTextSize(float TextSize) { this.mTextSize = TextSize; mTextPaint.setTextSize(mTextSize); invalidate(); } public void setProgressTextColor(int TextColor) { this.mTextColor = TextColor; mTextPaint.setColor(mTextColor); invalidate(); } public void setUnreachedBarColor(int BarColor) { this.mUnreachedBarColor = BarColor; mUnreachedBarPaint.setColor(mReachedBarColor); invalidate(); } public void setReachedBarColor(int ProgressColor) { this.mReachedBarColor = ProgressColor; mReachedBarPaint.setColor(mReachedBarColor); invalidate(); } public void setMax(int Max) { if(Max > 0){ this.mMax = Max; invalidate(); } } public void incrementProgressBy(int by){ if(by > 0){ setProgress(getProgress() + by); } } public void setProgress(int Progress) { if(Progress <= getMax() && Progress >= 0){ this.mProgress = Progress; invalidate(); } } @Override protected Parcelable onSaveInstanceState() { final Bundle bundle = new Bundle(); bundle.putParcelable(INSTANCE_STATE,super.onSaveInstanceState()); bundle.putInt(INSTANCE_TEXT_COLOR,getTextColor()); bundle.putFloat(INSTANCE_TEXT_SIZE, getProgressTextSize()); bundle.putFloat(INSTANCE_REACHED_BAR_HEIGHT,getReachedBarHeight()); bundle.putFloat(INSTANCE_UNREACHED_BAR_HEIGHT,getUnreachedBarHeight()); bundle.putInt(INSTANCE_REACHED_BAR_COLOR,getReachedBarColor()); bundle.putInt(INSTANCE_UNREACHED_BAR_COLOR,getUnreachedBarColor()); bundle.putInt(INSTANCE_MAX,getMax()); bundle.putInt(INSTANCE_PROGRESS,getProgress()); return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { if(state instanceof Bundle){ final Bundle bundle = (Bundle)state; mTextColor = bundle.getInt(INSTANCE_TEXT_COLOR); mTextSize = bundle.getFloat(INSTANCE_TEXT_SIZE); mReachedBarHeight = bundle.getFloat(INSTANCE_REACHED_BAR_HEIGHT); mUnreachedBarHeight = bundle.getFloat(INSTANCE_UNREACHED_BAR_HEIGHT); mReachedBarColor = bundle.getInt(INSTANCE_REACHED_BAR_COLOR); mUnreachedBarColor = bundle.getInt(INSTANCE_UNREACHED_BAR_COLOR); initializePainters(); setMax(bundle.getInt(INSTANCE_MAX)); setProgress(bundle.getInt(INSTANCE_PROGRESS)); super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATE)); return; } super.onRestoreInstanceState(state); } public float dp2px(float dp) { final float scale = getResources().getDisplayMetrics().density; return dp * scale + 0.5f; } public float sp2px(float sp){ final float scale = getResources().getDisplayMetrics().scaledDensity; return sp * scale; } public void setProgressTextVisibility(ProgressTextVisibility visibility){ if(visibility == ProgressTextVisibility.Visible){ mIfDrawText = true; }else{ mIfDrawText = false; } invalidate(); } }