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的位置,这在校准字体位置的时候非常实用!