基本概念 测量模式 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/