Android 自定义View

首先自定义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来获取。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容