NumberProgressBar開源項目學習
來自: http://blog.csdn.net/xsf50717/article/details/50550895
1、概述
多看多學漲姿勢, github真是個寶庫
這個項目主要是實現數字進度條效果
github地址在https://github.com/daimajia/NumberProgressBar
感謝開源作者!
梳理主要知識點:
【1】熟悉自定義view的流程
【2】實現原理
【3】android中的view坐標系使用
【4】onMeasure優雅的方法書寫
【5】canvas中drawText方法注意點
【6】代碼的可讀性非常強
2、項目要點分析
【熟悉自定義view的流程】
自定義view需要多多看別寫的精彩代碼,不過流程基本都是一致的在我的自定義View入門中有詳細介紹按照這個思路去分析自定義view即可
【本項目實現原理】
該項目比較基礎,適合作為入門學習項目,作者主要將自定義控件分為3大區域
mReachedRectF——Text區域(可以選擇沒有)——mUnreachedRectF
(該控件支持沒有text區域),主要是通過控制mReachedRectF和mUnreachedRectF的坐標來不斷地刷新ui來實現移動效果,沒有使用到動畫
自定義view 步驟 之獲取自定義屬性
這里作者直接寫到其中一個構造方法中
public NumberProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); 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_current, 0)); setMax(attributes.getInt(R.styleable.NumberProgressBar_progress_max, 100)); attributes.recycle(); initializePainters(); }
這里作者開始初始化自定義的屬性,通常我們可以單獨使用一個函數放在每個構造器下面
然后就是onMeasure,這里比較優雅,
其中EXACTLY主要針對match_parent/具體參數
ATMOST主要針對wrap_content情況這里做了處理,有時候我們如果把布局文件的寬和高寫成wrap_content,若此時父布局也為AT_MOST此時顯示的就是父布局的PraentSize
因此我們支持設置wrap_content時候需要重寫onMeasure方法,下面也是做了處理(AT_MOST)
鴻洋大神的自定義view博文對此也做了說明http://blog.csdn.net/lmj623565791/article/details/24252901
而UNSPECIFIED往往用于系統內部的測量通常只需要關注ATMOST和EXACTLY
@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; }
onDrawer方法的可讀性更是6666
有時候我們也要注意寫出“可以說話的代碼”,注意函數的封裝
@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); }
這段代碼我們可以清晰的看出作者的邏輯 要是設置了數字顯示執行 calculateDrawRectF()否則執行 calculateDrawRectFWithoutProgressText()
這倆個函數就是開始處理前文提到的mReachedRectF和mUnreachedRectF倆個矩形的位置變化,這里需要熟悉一下android中的坐標系和點擊位置的獲取
看一下有文字時候計算倆個區域的情況主要集中處理了開始階段和最終階段的文字未知的特殊情況
private void calculateDrawRectF() { mCurrentDrawText = String.format("%d", getProgress() * 100 / getMax()); mCurrentDrawText = mPrefix + mCurrentDrawText + mSuffix;//轉換成字符串 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);//實際中右位置 } //mTextPaint.descent() + mTextPaint.ascent() baseLine到字體最高+baseLine到字體最低=實際字體高度 mDrawTextEnd = (int) ((getHeight() / 2.0f) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f)); if ((mDrawTextStart + mDrawTextWidth) >= getWidth() - getPaddingRight()) { //文字終點位置重置文字起始位置和mReachedRectF矩形的right 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.top = getHeight() / 2.0f -mUnreachedBarHeight / 2.0f; mUnreachedRectF.right = getWidth() - getPaddingRight(); mUnreachedRectF.bottom = getHeight() / 2.0f + mUnreachedBarHeight / 2.0f; } }
這里主要提下mTextPaint .descent() + mTextPaint .ascent()的這里參照的距離都是baseline,這樣可以計算出整個字體的高度,具體可以參考
http://mikewang.blog.51cto.com/3826268/871765/ 詳細講解了androd的字體屬性和測量
還有canvas.drawText方法中xy坐標其實是baseline的位置,這在校準字體位置的時候很有用!