《Android开发艺术探索》第四章View的工作原理小结

来源:互联网 发布:vb modbus 编辑:程序博客网 时间:2024/05/29 14:37

1. ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,它连接WindowManager和DecorView。View的绘制流程从ViewRoot的performTraversals开始,经过measure,layout和draw过程。

Measure决定了View 的宽和高,通过getMeasuredWidth和getMeasuredHeight来获得;

Layout决定了四个顶点的坐标和实际的View宽高,之后可调用getTop, getBottom, getLeft, getRight, getWidth和getHeight;

Draw决定了View的显示。


2. MeasureSpec

在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,得出View的测量宽高。

MeasureSpec代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize。

SpecMode取值为EXACTLY,对应LayoutParams的match_parent和具体数值两种方式,表示View的最终大小就是父容器SpecSize所指定的值;

AT_MOST,对应LayoutParams的wrap_content,表示父容器制定了一个SpecSize,View不能大于它。

子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和View的margin以及padding有关。

如果View为固定宽高,则View的SpecMode都是EXACTLY并且大小为LayoutParams中的大小;

如果View为match_parent,则View的SpecMode与父容器相同,SpecSize也为parentSize;

如果View为wrap_content,则View的SpecMode都是AT_MOST,并且SpecSize为parentSize。


3. View的onMeasure

setMeasureDimension设置宽高的测量值,getDefaultSize返回measureSpec中的specSize,getSuggestedMinimumWidth如果View没有设置背景则返回android:minWidth,否则返回minWidth和背景最小宽度中最小的。

直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则默认的wrap_content就相当于match_parent。

重写onMeasure:在AT_MOST的情况下,将尺寸设置为自己内部算好的尺寸。


4. ViewGroup的measure

除了完成自己的measure,还会便利调用所有子元素的measure方法。measureChildren方法,对非GONE的子View执行measureChild。

measureChild:根据父节点的MeasureSpec,结合子节点的LayoutParams,创建子节点的MeasureSpec,传入child.measure方法。

某些极端情况下,需要多次measure才能确定最终测量宽高,所以应在onLayout中获取View 的测量宽高或者最终宽高。

以LinearLayout的measureVertical(widthMeasureSpec, heightMeasureSpec)为例,计算每个子元素的初步高度,再累加;

如果它的布局中高度采用的是match_parent或者具体数值,则高度即为specSize,如果采用了wrap_content则高度是累加值但是不能超过父容器的空间剩余;


5. 四种方式获取View的尺寸

View的measure过程和Activity的生命周期方法不是同步执行的,所以无法保证在onCreate, onStart, onResume时某个View已经测量完毕。

1)onWindowFocusChanged,View初始化完毕,宽高已经准备好,在Activity的窗口得到焦点和失去时均会被调用;

2)view.post(runnable),通过post将一个runnable投递到消息队列的队尾,然后等待Looper调用;

3)ViewTreeObserver的OnGlobalLayoutListener接口,view树的状态发生改变或者内部View可见性发生改变;

4)view.measure

int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30) - 1, MeasureSpec.AT_MOST);

view.measure(widthMeasureSpec, heightMeasureSpec);


6. layout过程

layout的大致流程:首先通过setFrame来设定View的四个顶点位置,接着调用onLayout确定子元素的位置;

以LinerLayout的onLayout中的layoutVertical为例,遍历所有子元素并调用setChildFrame设置子元素的位置,top逐渐增大,setChildFrame内部又调用了child.layout;

测量宽高形成于View的measure过程,最终宽高形成于layout过程。


7. draw过程

1)绘制背景background.draw(canvas)

2)绘制自己onDraw(canvas)

3)绘制子元素dispatchDraw(canvas)

4)绘制装饰onDrawScrollBars(canvas)


8. 自定义View方式

1)继承View重写onDraw:实现一些不规则的图形,要自己支持wrap_content,并且padding也需要自己处理;

2)继承ViewGroup派生特殊的Layout:处理ViewGroup和子元素的测量和布局两个过程;

3)继承特定的View:例如TextView;

4)继承特定的ViewGroup:例如LinerLayout


9. 自定义View须知

1)支持wrap_content

2)直接继承View的控件,需要在draw方法中处理padding,直接继承ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin;

3)尽量不要在View中使用Handler,因为View本身支持post方法;

4)View中有线程或者动画:可以在onAttachedToWindow中开启,在onDetachedFromWindow时停止和关闭;

5)滑动嵌套时要解决滑动冲突;


10. 自定义属性

1)在values目录下创建attrs_xxxx.xml,声明一个自定义属性集合,在其中定义属性名和格式;

 <resources>

    <declare-styleable name="CircleView">

        <attr name="circle_color" format="color" />

    </declare-styleable>

</resources>

2)在View的构造方法中解析自定义属性的值

public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {

    super(context, attrs, defStyleAttr);

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);

    mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);

    a.recycle();

    init();

}

3)在布局文件中使用自定义属性

xmlns:app="http://schemas.android.com/apk/res-auto"

app:circle_color="@color/light_green"


0 0
原创粉丝点击