我们所有的试图都是起源于自定义View,包括ViewGroup也是继承于它,可以说它是视图组件之父。
我们可以从它的大致流程来分为四个部分:
构造方法,onMeasure,onLayout,onDraw
构造方法:
它主要有四个构造方法
分别为:
- View(Context context)
- public View(Context context, @Nullable AttributeSet attrs)
- public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
- public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)
关于attrs,defSyleAttr,defStyleRes介绍,源码中是这样说的
(一) attrs
attrs是一个int值,通过它一系列xml中一系列属性值的一个
它的作用:
- 可以通过它来获取布局文件资源id值
- 在saveAttributeData中会默认先加载系统默认的属性值跟id,然后通过attrs加载已解析的可样式属性,比如说layout_marginTop,系统会给它一个默认的值,如果我们在xml当中进行修改,然后会将原值进行替换。
(二)defStyleAttr
Framework中源文件参数的介绍:
defStyleAttr An attribute in the current theme that contains a reference to a style resource that supplies default values for the view. Can be 0 to not look for defaults.
(三)defStyleRes介绍
defStyleRes A resource identifier of a style resource that supplies default values for the view, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults
这两个参数在saveAttributeDataForStyleable中有参与但是看不到具体的内容
onMeasure
它最终会调到
MeasureSpec:
MeasureSpec是一个32位的int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,SpecSize是指某种测量模式下的规格大小。
- EXACTLY 父容器已经检测出View所需要的精确大小,这时候View的最终大小就是SpecSize所指定的值。对应于LauoutParams中的match_parent和具体数值这两种模式。
- AT_MOST 父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,对应于LayoutParams中的wrap_content
- UNSPECIFIED 父容器不对View有任何限制,要多大给多大,常用于系统内部,平常工作中不常见
普通View的MeasureSpec的创建规则:
onMeasure会调到setMeasureDimension方法
它会设置View 宽/高的测量值,因此只需要看getDefaultSize方法
从这个方法来看,View的宽高由specSize决定,再结合MeasureSpec的创建规则表,得出结论:
直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。因为 View使用wrap,它的specMode是AT_MOST模式,宽高等于specSize,这情况下specSize为parentSize,而parentSize是父容器可使用的大小。
解决方法:
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightMeasureSpec);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthMeasureSpec, mHeight);
}
onLayout
onLayout主要是视图位置的确定
onDraw
View的回值过程遵循如下几步:
- 绘制背景 background.draw
- 绘制自己 onDraw
- 绘制child dispatchDraw
- 绘制装饰 onDrawScrollBars
常见Canvas方法:
Paint常用方法:
paint.setStrokeWidth(0);//设置画笔宽度0 ,单位px 默认一个像素
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.STROKE);//描边
paint.setStrokeWidth(10);//画笔宽度 10,单位px
paint.setAntiAlias(true);//抗锯齿功能
paint.setAlpha(); //设置画笔透明度
paint.setARGB();// 设置透明度 和 颜色
paint.setStyle()//设置画笔样式
注意:
- 尽量不要在View中使用Handler
- View中如果由线程或者动画,需要及时停止,可以在View#onDetachedFromWindow操作
- 有嵌套滑动情形时,需要处理好滑动冲突