您的位置:首页 > 移动开发 > Android开发

Android View系统解析(下)

2014-08-09 16:39 274 查看

View的measure 流程

view 的 measure 流程

简单,直接完成

ViewGroup 的 measure 流程

除了完成自己的 measure ,还会遍历去调用所有 child 的measure 方法,各个 child 再递归去执行这个流程

measure 的直接结果

getMeasuredWidth/Height 可以正确地获取到

注:某些情况下,系统可能需要多次 measure 才能确定大小

在渲染前获取 View 的宽高

这是一个比较有意义的问题,或者说有难度的问题,问题的背景为:有时候我们需要在view渲染前去获取其宽高,典型的情形是,我们想在onCreate、onStart、onResume中去获取view的宽高。如果大家尝试过,会发现,这个时候view还没有measure好,宽高都为0,那到底该怎么做才能正确获取其宽高呢,下面给出三种方法

Activity/View#onWindowFocusChanged :这个方法表明,view已经初始化完毕了,宽高已经准备好了

view.post(runnable) :通过post可以将一个runnable投递到消息队列的尾部,然后等待looper调用此runnable的时候,view也已经初始化好了

view.measure(int widthMeasureSpec, int heightMeasureSpec) :通过手动去measure来视图得到view的宽高

前两种方法都比较好理解也比较简单,这里主要介绍下第三种方法的详细用法:

采用 view.measure 去提前获取 view 的宽高,根据 view 的 layoutParams 来分

match_parent

直接放弃,无法 measure 出具体的宽高

具体的数值( dp/px )

比如宽高都是 100px ,如下 measure :

int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);

int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);

view.measure(widthMeasureSpec, heightMeasureSpec);

wrap_content

如下 measure :

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

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

view.measure(widthMeasureSpec, heightMeasureSpec);

注意到(1 << 30) - 1,通过分析MeasureSpec的实现可以知道,view的尺寸使用30位二进制表示的,也就是说最大是30个1即 2^30 - 1,也就是(1 << 30) - 1,在最大化模式下,我们用view理论上能支持的最大值去构造MeasureSpec是合理的。

关于view的measure,网络上有两个错误的用法,如下,为什么说是错误的,首先违背了系统的内部实现规范(因为无法通过错误的MeasureSpec去得出合法的SpecMode从而导致measure出错),其次不能保证一定能 measure 出正确的结果。

第一种错误用法

int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);

int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);

view.measure(widthMeasureSpec, heightMeasureSpec);

第二种错误用法

view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)

View 的 layout 过程

layout 的主要作用

ViewGroup 用来确定子元素的位置。

流程

当 viewgroup 的位置被确定后,它在 onLayout 会遍历所有的 child 并调用其 layout 。在 layout 中 onLayout 会被调用。

关键方法

public void layout(int l, int t, int r, int b)

onLayout(changed, l, t, r, b)

构造特殊的 View

问题:如何让 getWidth 和 getMeasuredWidth 返回的值不一样?

private void setChildFrame(View child, int left, int top, int measuredWidth, int measureHeight) {

child.layout(left, top, left + measuredWidth, top + measureHeight);

}

int width = right - left;

int height = bottom - top

方法

在父容器的 onLayout 中通过 child.layout 来放置 view 到任意位置

在自己的 onLayout 中修改 mLeft/mRight/mTop/mBottom

View 的 draw 过程

draw 的大致流程

a. 画背景 background.draw(canvas)

b. 绘制自己( onDraw )

c. 绘制 children ( dispatchDraw )

d. 绘制装饰( onDrawScrollBars )

备注:

dispatchDraw 会遍历调用所有 child 的 draw ,如此 draw 事件就一层层地传递了下去

二 自定义 View

自定义View类型

继承 View 重写 onDraw

继承 ViewGroup 派生特定的 Layout

继承特定的 View (比如 TextView , ListView )

继承特定的 Layout (比如 LinearLayout )

自定义View须知

让 view 支持 wrap_content

如果有必要,让你的 view 支持 padding

尽量不要在 view 中使用 Handler ,没必要

view 中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow

view 带有滑动嵌套情形时,需要处理好滑动冲突
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: