第四章 View 的工作原理

4.3 View 的工作流程

4.3.1 measure 过程
1. View 的 measure 过程
View 的measure()是一个 final 类型的方法,不能被重写,其中调用了 View 的 onMeasure(),

https://blog.csdn.net/ghdsq/article/details/79714465

4.3.2 measure 的总结
measure 之后,通过getMeasuredHeight()可以获取到测量后的宽高,在某些极端情况下,系统可能多次测量后才能确定最终的宽高,这种情况下,在onMeasure中拿到的测量宽高是不准确的,一个比较好的习惯是在onLayout方法中获取 View 的测量宽高或最终宽高。
在 Activity 的onCreate、onStart、onResume均无法得到准确的宽高,因为无法保证此时 View 已经测量完毕。四种方法解决:
1. 该方法含义是:View 已经初始化完毕,宽和高已经准备好,此时获取宽高是可以的。在 Activity 窗口得到焦点和失去焦点时均会调用一次。如果 Activity 频繁地进行onResumeonPause,该方法也会频繁执行
2. 通过 post 可以将一个 runnable 投递到消息队列的尾部,等待 Looper 调用此 Runnable 的时候,View 也已经初始化好了。
3. 使用 ViewTreeObserver 的众多回调可以完成这个功能。使用OnGlobalLayoutListener接口,当View树的状态发生改变或者View树内部的View的状态发生改变该方法会被调用,这是获取View的宽高的一个很好的时机。伴随着View树的状态改变等,该方法会被执行多次。
4. 通过View.measure(int widthMeasureSpec, int heightMeasureSpec)
根据 View 的 LayoutParams 来分:
当宽和高是 Match_parent,无法测量出具体宽高。因为不知道父容器的声誉空间。
当宽高是 Wrap_content,利用最大化模式下支持的最大值去构造 MeasureSpec:

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
textView.measure(widthMeasureSpec, heightMeasureSpec);
int measuredHeight = textView.getMeasuredHeight();

当宽和高是具体的数值,例如100px,可以直接测量:

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
textView.measure(widthMeasureSpec, heightMeasureSpec);
int measuredHeight = textView.getMeasuredHeight();

4.3.3 layout 的总结
Layout 的作用是 ViewGroup 用来确定子元素的位置,当 ViewGroup 的位置确定后,再 onLayout 中会遍历所有的子元素并调用其 layout 方法确定自身位置,在 layout 中 onLayout 又会被调用来确定子元素的位置。在 View 和 ViewGroup 中,onLayout 没有具体实现,因为每一种 view 情况都不一样。拿 LinearLayout 距离来看:
它的父容器会调用它的 layout 方法:

layout() -> onLayout() -> layoutVertical -> setChildFrame() -> child.layout() -> (子)layout() -> (子)onLayout()

setFrame:在 layout() 方法中调用,设定view的四个顶点的位置,即初始化mLeft、mTop、mBottom、mRight, 四个顶点一旦确定,View在父容器的位置也就确定了。
setChildFrame():LinearLayout 中的方法,仅仅调用了child.layout(left, top, left + width, top + height);

4.3.4 layout 的总结
View 的测量宽高和最终宽高有什么区别?

https://blog.csdn.net/ghdsq/article/details/79708961

4.3.5 draw 过程
View 的流程:从父容器的 dispatchDraw开始:
dispatchDraw() -> drawChild() -> child.draw() -> (子) draw() -> (子)onDraw()
子View中 draw() 的绘制过程:
1. 绘制背景:drawBackground()
2. 绘制自己:onDraw()
3. 绘制孩子:dispatchDraw()
4. 绘制装饰: onDrawForeground()

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */
        public void draw(Canvas canvas) {
            if (!dirtyOpaque) {
                /**
                 * 绘制背景
                 */
                drawBackground(canvas);
            }
                /**
                 * 绘制自己
                 */
            if (!dirtyOpaque) onDraw(canvas);
            /**
             * 绘制孩子
             */
            dispatchDraw(canvas);
            // Step 6, draw decorations (foreground, scrollbars)
            /**
             * 绘制装饰
             */
            onDrawForeground(canvas);
            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);
        }

dispatchDraw(canvas):View绘制过程的传递是通过该方法来实现的,会调用所有的子 View 的 draw 方法,如此 draw 事件就一层层地传递下去。
setWillNotDraw(boolean willNotDraw):如果一个view不需要绘制任何内容,那么设置这个标记位为true后,系统会进行相应的优化。默认情况下,View没有启用这个优化标记位,但是ViewGroup会默认启用这个优化标记位,这个标记位对实际开发的意义:当我们的自定义控件继承与ViewGroup并且本身不具备绘制功能的时候,就可以开启这个标记位便于系统进行后续的优化。如果明确知道一个ViewGroup 需要通过onDraw来绘制内容,我们需要显示关闭WILL_NOT_DRAW这个标记位

4.4 自定义 View

自定义View涉及 View 的层次结构、事件分发机制和 View 的工作原理等技术细节。
分类:
1. 继承 View 重写 onDraw 方法
主要实现一些不规则的效果,需要自己支持 wrap_content,并且 padding 也需要自己处理。
2. 继承 ViewGroup 派生特殊的 Layout
当某种效果看起来很像几种 View 组合在一起的时候,可以采用这种方法来实现。需要合适处理 ViewGroup 的测量、布局两个过程,并同时处理子 View 的测量、布局。
3. 继承特定的 View (比如TextView)
扩展已有View的功能。
4. 继承特定的 ViewGroup(比如LinearLayout)
当某种效果看起来很像集中 View组合在一起的时候,采用该方法。不需要自己处理 ViewGroup 的测量和布局两个过程,一般来说,方法 2 中能实现的方法 4 也能实现。
注意:
1. 让 View 支持 Wrap_content
直接继承 View 和 ViewGroup 的控件,如果不在 onMeasure 中对 wrap_content 做特殊处理,则当外界在布局中使用 wrap_content 的时候是无效的。
2. 如果有必要,让你的 View 支持 padding
直接继承 View 的控件,如果不在 draw 方法中处理 padding,那么 padding 属性是无法起作用的。
直接继承 ViewGroup 的控件需要在 onMeasure 和 onLayout 中考虑 padding 和子元素的 margin 对其造成的影响,不然会导致无效。
3. 尽量不要在 View 中使用 Handler,没必要
因为 View 内部本身提供了 post 方法,完全可以替代 Handler 的作用。
4. View 中线程或者动画需要及时停止,参考 View#onDetachedFromWindow
当包含此 View 的 Activity 退出或者当前的 View 被 remove时,View 的onDetachedFromWindow会被调用,此时是停止线程或者动画的最好的时机。同时,当 View 变得不可见时我们也需要停止线程和动画,如果不及时处理这种问题,有可能会造成内存泄漏。
当包含此 View 的 Activity 启动时,View 的onAttachedToWindow()会被调用。
5. View 带有滑动嵌套情形时,需要处理好滑动冲突

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值