Android中View绘制流程

 

View的工作流程主要分为onMeasure、onSizeChanged、onLayout、onDraw;

 

 

onMeasure

onMeasure方法在控件的父元素正要放置它的子控件时调用。它会问一个问题,“你想要用多大地方啊?”,然后传入两个参数——widthMeasureSpec和heightMeasureSpec。它们指明控件可获得的空间以及关于这个空间描述的元数据。具体使用如下所示:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
        int width = 0;
        int height = 0;
        // 记录每一行的宽度与高度
        int lineWidth = 0;
        int lineHeight = 0;

        // 得到内部元素的个数
        int cCount = getChildCount();

        for (int i = 0; i < cCount; i++) {
            View child = getChildAt(i);
            // 测量子View的宽和高
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            // 得到LayoutParams
            MarginLayoutParams lp = (MarginLayoutParams) child
                    .getLayoutParams();

            // 子View占据的宽度
            int childWidth = child.getMeasuredWidth() + lp.leftMargin
                    + lp.rightMargin;
            child.setLayoutParams(lp);
            // 子View占据的高度
            int childHeight = child.getMeasuredHeight() + lp.topMargin
                    + lp.bottomMargin;

            // 换行
            if (lineWidth + childWidth > sizeWidth - getPaddingLeft()
                    - getPaddingRight()) {
                // 对比得到最大的宽度
                width = Math.max(width, lineWidth);
                // 重置lineWidth
                lineWidth = childWidth;
                // 记录行高
                height += lineHeight;
                lineHeight = childHeight;
            } else
            // 未换行
            {
                // 叠加行宽
                lineWidth += childWidth;
                // 得到当前行最大的高度
                lineHeight = Math.max(lineHeight, childHeight);
            }
            // 最后一个控件
            if (i == cCount - 1) {
                width = Math.max(lineWidth, width);
                height += lineHeight;
            }
        }

        setMeasuredDimension(
                //
                modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width
                        + getPaddingLeft() + getPaddingRight(),
                modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height
                        + getPaddingTop() + getPaddingBottom()//
        );

    }

MeasureSpec

MeasureSpec代表一个32位int值,高两位代表SpecMode,低30位代表SpecSize,封装了父容器对 view 的布局上的限制,内部提供了宽高的信息( SpecMode 、 SpecSize ),SpecSize是指在某种SpecMode下的参考尺寸。 
SpecMode值有如下三种:

  • UNSPECIFIED:父容器不对 view 有任何限制,要多大给多大
  • EXACTLY:父容器已经检测出 view 所需要的大小
  • AT_MOST:父容器指定了一个大小, view 的大小不能大于这个值

 

关于应用层View,这里是指我们布局中的view,其MeasureSpec的创建遵循下表中的规则

针对上表,这里再做一下具体的说明。前面已经提到,对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自 
身的 LayoutParams 来共同决定,那么针对不同的父容器和view本身不同的LayoutParams,view就可以有多种MeasureSpec。这里简单说下,当view采用固定宽高的时候,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式并且其大小遵循Layoutparams中的大小;当view的宽高是match_parent时,这个时候如果父容器的模式是精准模式,那么view也是精准模式并且其大小是父容器的剩余空间,如果父容器是最大模式,那么view也是最大模式并且其大小不会超过父容器的剩余空间;当view的宽高是wrap_content时,不管父容器的模式是精准还是最大化,view的模式总是最大化并且大小不能超过父容器的剩余空间。可能大家会发现,在我们的分析中漏掉了Unspecified模式,这个模式主要用于系统内部多次measure的情况下,一般来说,我们不需要关注此模式。

onSizeChanged(int w, int h, int oldw, int oldh)

这个是系统回调方法,在这个View的大小发生改变的时候会被系统调用,我们要监控view的大小变化,重写这个方法就可以了。

onLayout

主要作用:为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。当 viewgroup 的位置被确定后,它在 onLayout 会遍历所有的 child 并调用其 layout 。在 layout 中 onLayout 会被调用。 

onDraw

绘图时在onDraw中绘制,如果是GONE状态下,View的绘制周期会在onLayout的时候停止,不会执行这个方法。 
draw 的大致流程

a. 画背景 background.draw(canvas) 
b. 绘制自己( onDraw ) 
c. 绘制 children(dispatchDraw ) 
d. 绘制装饰( onDrawScrollBars ) 
备注:dispatchDraw 会遍历调用所有 child 的 draw ,如此 draw 事件就一层层地传递了下去

 

invalidate

用于刷新View 
说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”视图,即谁(View的话,只绘制该View;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。 
一般引起invalidate()操作的函数如下:

  1. 直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
  2. setSelection()方法:请求重新draw(),但只会绘制调用者本身,listview.setselection(position),表示将列表移动到指定的Position处。
  3. setVisibility()方法:当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。
  4. setEnabled()方法:请求重新draw(),但不会重新绘制任何视图包括该调用者本身。

自定义View注意事项

  1. 让MyView支持Warp_content 如果不在onMeasure()方法中对warp_content做特殊处理,就不能达预期的效果。 
    解决方法是对在onMeasure()方法中做相应的处理,把需要设定的宽或者高,设置成匹配内容宽高的int值,当然也要考虑内边距padding。 
    思路如下:判断widthMeasureSpec和heightMeasureSpec是否为AT_MOST,也就是wrap_content,如果想让实际宽度为占满父控件,就把widthSpecSize当做参数,如果不想这样,想要自己定义宽度,就填入具体数值,也可以计算内容的宽度来作为参数。 
     

  2. 让MyView支持padding 这是因为直接继承了View的控件,如果不在draw方法中处理padding,那么padding属性是无法起作用的。另外,如果直接继承自ViewGroup的控件,需要在onMeasure()和onLayout()中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素的margin失效。(padding是内边距,需要控件自己控制,而margin是外边距,由父控件影响) 
     

  3. 尽量不要在View中使用Handler,因为View本身就提供了post系列的方法,完全可以替代Handler的作用。 
     
  4. View中如有线程或者动画,需要及时停止: 如果有线程或者动画需要停止时,那么onDetachedFromWindow是一个很好的时机。当包含此View的Activity退出或者当前View被remove时,View的onDetachedFromWindow方法会被调用,和此方法对应的是onAttachedToWindow方法会被调用。当View变得不可见时也需要停止线程和动画,如果不及时处理这种问题,有可能会造成内存泄漏。 
     
  5. 注意特殊情况下的View滑动冲突

 

总结
从View的测量、布局和绘制原理来看,要实现自定义View,根据自定义View的种
类不同,可能分别要自定义实现不同的方法。但是这些方法不外乎:onMeasure()
方法,onLayout()方法,onDraw()方法。
onMeasure()方法:单一View,一般重写此方法,针对wrap_content情况,规定
View默认的大小值,避免于match_parent情况一致。ViewGroup,若不重写,就会
执行和单子View中相同逻辑,不会测量子View。一般会重写onMeasure()方法,循
环测量子View。
onLayout()方法:单一View,不需要实现该方法。ViewGroup必须实现,该方法是
个抽象方法,实现该方法,来对子View进行布局。

onDraw()方法:无论单一View,或者ViewGroup都需要实现该方法,因其是个空
方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值