基本概念 测量模式 MeasureSpec MeasureSpec 由两部分组成:
mode : 测量模式
size : 测量的尺寸大小
其中模式有三种:
UNSPECIFIEDViewGroup 没有做约束,想要多大就多大,一般用于系统内部,如 ListView 等。
EXACTLY 默认模式,按照给定的值精确计算,具体高宽值和 match_parent 都是这种模式。
AT_MOST 相当于 wrap_content ,根据自身的内容的高宽来计算。
View 根据 ViewGroup 传入的测量值和模式,对自己宽高进行确定,并完成绘制。 onMeasure 实现测量,然后在 onDraw 中完成对自己的绘制。
重要 API
onMeasure 测量,决定高宽等,不是必须但大部分都会重写,重写主要需要针对 wrap_content 模式计算自身实际的高宽,通过调用 setMeasuredDimension 来设置。如果指定具体的高宽或者 match_parent 可以不用重写,父类默认是以这种方式来测量的。
onDraw 绘制,即如何展现,必须重写
自定义 View 步骤 自定义属性和样式 在 res/values/ 下建立一个 attrs.xml , 在里面定义 View 的属性和声明整个样式。
1 2 3 4 5 <declare-styleable name ="CustomView" > <attr name ="custom_text" format ="string" /> <attr name ="custom_color" format ="color" /> <attr name ="custom_size" format ="integer" /> </declare-styleable >
其中,format 一共有如下几种类型:string, boolean, color, dimension, enum, flag, float, fraction, integer, reference。 在布局文件中需要引入这个自定义的属性,先加上这一句(老版本需要手动导入自定义 View 的包名):xmlns:app="http://schemas.android.com/apk/res-auto"。后续就可以通过 app:custom*** 来设置自定义的属性了,如下所示:
1 2 3 4 5 6 7 8 <com.***.view.CustomView android:id ="@+id/view_event_custom_view_button" android:layout_width ="wrap_content" android:layout_height ="wrap_content" // 自定义属性 app:custom_text ="@string/view_event_custom_view_button" app:custom_color ="@color/colorYellow" app:custom_size ="@dimen/smallTextSize" />
在构造方法中获得自定义属性的值 解析自定义属性时,注意 styleable 是通过 declare-styleable 中名称拼接来的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 final Resources.Theme theme = context.getTheme();TypedArray a = theme.obtainStyledAttributes(attrs, R.styleable.CustomView, defStyleAttr, 0 ); int n = a.getIndexCount();for (int i = 0 ; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.CustomView_custom_text: mText = a.getText(attr).toString(); break ; case R.styleable.CustomView_custom_color: mColor = a.getColor(attr, Color.BLACK); break ; case R.styleable.CustomView_custom_size: int defaultSize = (int ) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, 24 , getResources().getDisplayMetrics()); mSize = a.getDimensionPixelSize(attr, defaultSize); break ; } }
重写 onMesure 不是必须的,但是大部分都会重写。如果没有重写,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果;当我们设置为 WRAP_CONTENT 或者 MATCH_PARENT 系统帮我们测量的结果就是 MATCH_PARENT 的长度。所以当设置了 WRAP_CONTENT 时,需要代码中进行测量,即重写 onMesure 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super .onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); Log.d(TAG, "onMeasure: widthMeasureSpec = " + MeasureSpec.toString(widthMeasureSpec) + ", heightMeasureSpec = " + MeasureSpec.toString(heightMeasureSpec)); mPaint.setTextSize(mSize); mPaint.getTextBounds(mText, 0 , mText.length(), mBound); int measureWidth, measureHeight; if (widthMode == MeasureSpec.EXACTLY){ measureWidth = widthSize; }else { float textWidth = mBound.width(); measureWidth = (int ) (getPaddingLeft() + textWidth + getPaddingRight()); } if (heightMode == MeasureSpec.EXACTLY){ measureHeight = heightSize; }else { float textHeight = mBound.height(); measureHeight = (int ) (getPaddingTop() + textHeight + getPaddingBottom()); } setMeasuredDimension(measureWidth, measureHeight); }
重写 onDraw 主要是通过 Paint 和 Canvas 将需要表达的内容画出来。本例只是仿照 TextView 显示一段文本比较简单,所以只需要画出文本就行。
1 2 3 4 5 6 7 8 9 10 11 12 @Override protected void onDraw (Canvas canvas) { super .onDraw(canvas); mPaint.setColor(Color.BLUE); canvas.drawRect(0 , 0 , getMeasuredWidth(), getMeasuredHeight(), mPaint); mPaint.setColor(mColor); canvas.drawText(mText, getWidth()/2 - mBound.width()/2 , getHeight()/2 + mBound.height()/2 , mPaint); }
View.draw() 的流程1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public void draw (Canvas canvas) {drawBackground(canvas); onDraw(canvas); dispatchDraw(canvas); onDrawForeground(canvas); ... }
重新布局和绘制 API
requestLayout 会调用 onMeasure 和 onLayout 进行重新测量及布局,但不会调用 draw 的过程,不会重新绘制任何 View 包括该调用者本身
invalidate 只能在 UI 线程中执行。请求重绘 View (也就是 draw方法),哪个 View 请求 invalidate 系列方法,就重绘该 View。即 View 只绘制该 View,ViewGroup 绘制整个 ViewGroup
postInvalidate 非 UI 线程中请求重绘 View
示例 CustomView 设置文本时请求重新布局和绘制
1 2 3 4 5 public void setText (String text) { mText = text; requestLayout(); invalidate(); }
目标
自定义 View 常见流程
View 的绘制
处理事件分发流程
参考文档
http://blog.csdn.net/lmj623565791/article/details/24252901/
http://blog.csdn.net/congqingbin/article/details/7869730
http://blog.csdn.net/yanbober/article/details/46128379/