首先自定义View分几种类型
1、继承View
2、继承ViewGroup
3、继承View的派生类,比如TextView, 类似于扩展功能
4、继承ViewGroup的派生类,比如LinearLayout, 扩展功能,用于实现某几种组合的布局
继承自View 跟ViewGroup大多数都是加载定义好的layout,或者是重写某些方法实现新的功能,这些都不涉及View的绘制过程,当然需要注意的地方,就是它的四个构造函数之间的区别。
View的四大构造函数
View(Context context)
这个构造函数用context实例构建一个view,但是没有view的任何属性,需要自己手动设置,一般比较少使用
View(Context context, @Nullable AttributeSet attrs)
我们在XML文件里加入的控件都有调用这个构造函数,第二个参数可以获取到xml定义的所有属性。
TypedArray a = context.obtainStyledAttribute(attrs, R.styleable.CustomView)
通过TypedArray获取定义好的属性。
View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)
后面两个都是跟主题相关的,defStyleAttr是定义在主题中的属性,比如下面的@style/BlueTextStyle,defStyleRes是内置于view的style
<style name="AppTheme" parent="@android:style/Theme.Holo">
<item name="android:textViewStyle">@style/BlueTextStyle</item>
<item name="android:textColor">@android:color/holo_purple</item>
</style>
如果多个地方都定义了某个属性,他们的优先级如下:
直接xml定义> xml中引用Theme> defStyleAttr>defStyleRes> theme中定义
下面进入正式的主题View的绘制流程。
MeasureSpec
首先,先介绍一下MeasureSpec,MeasureSpec分为SpecMode跟SpecSize, android 为了节省空间,采用了一个32位的int来表示一个MeasureSpec,而高两位表示SpecMode,测量的模式,后三十位表示SpecSize,测量的规格大小。
int makeMeasureSpec(int size, int mode) 方法可以把size 跟mode 打包成一个int.
int getMode(int measureSpec) 获取mode
int getSize(int measureSpec) 获取size
SpecMode 有三类,
UNSPECIFIED 父容器不对view做限制,要多大给多大,一般用于系统内部。
EXACTLY 表示父容器已经检测到view的精确的大小,这个时候view的大小就是SpecSize的大小,对应View的具体的数值跟LayoutParam的Match_parent
AT_MOST 父容器制定了一个可用大小即SpecSize,view的大小不能超过该大小,对应view的LayoutParam的wrap_content
普通view的MeasureSpec的大小由自身的LayoutParams以及父容器的MeasureSpec决定,包括父容器的margin和padding等,进一步确定view的宽高。
顶级view(DecorView)的MeasureSpec测量由Window的尺寸,以及自身的LayoutParams决定。
View的绘制流程包含measure、layout、draw三大流程。
measure流程
View的测量直接通过measure方法就可以了,而ViewGroup需要先测量自己,然后再measure所有的子view。
View的measure方法是final类型,内部调用了onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
setMeasuredDimension会设置view的宽高,getDefaultSize获取view的宽高,从这里可以看出,如果我们不对wrap_content做处理,我们在xml中定义wrap_content,它的效果跟match_parent一样。如果我们想支持wrap_content,只需要对SpecMode为AT_MOST的情况进行处理,按照需要获取宽高,最后调用setMeasuredDimension方法。
ViewGroup的measure过程
ViewGroup没有重写onMeasure方法,但是提供了measureChildren方法,里面主要是遍历子view,并调用measureChild(),而measureChild方法里根据子view的layoutParams创建childMeasureSpec, 然后调用子view的measure方法
每种ViewGroup都有不同的测量方法,这里以LinearLayout为例,它在onMeasure方法中,根据mOrientation来调用measureVertical或者measureHorizontal
measureVertical中遍历调用子view的measure方法获取每个view的高度,最后加上LinearLayout自己的高度,如果LinearLayout的SpecMode是match_parent或者是具体的数值,它跟view的测量一样,但是如果是wrap_content,那它所有高度的总和不能超过父容器的剩余空间,还要考虑竖直方向的padding.
这里讲一下我们在activity生命周期里获取view的宽高,很大概率获取到0. 因为在activity的生命周期内,无法保证view已经绘制完成。如果需要获取宽高,一般由一下几种方案。
1、activity/view# onWindowFocusChanged。
这个方法表示view已经绘制完成了,可以获取宽高,但是这方法会调用多次。
2、view.post(runnable)
3、ViewTreeObserver
ViewTreeObserver的几个回调可以获取宽高,比如OnGlobalLayoutListener
4、view.measure()手动获取宽高
如果view的layoutParams是match_parent,我们拿不到父容器的SpecSize, 肯定获取不到宽高,
如果是固定值,可以通过MeasureSpec.makeMeasureSpec(100,MeasureSpec.AT_MOST)获取
如果是wrap_content,可以用理论上最大的size来获取。