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 带有滑动嵌套情形时,需要处理好滑动冲突