Android中View绘制流程

来源:互联网 发布:python android接口 编辑:程序博客网 时间:2024/06/05 04:04

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

      • onMeasure
        • MeasureSpec
      • onSizeChangedint w int h int oldw int oldh
      • onLayout
      • onDraw
      • invalidate
      • 自定义View注意事项

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 会被调用。 
/** 
* 存储所有的View 
*/ 
private List

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滑动冲突
0 0
原创粉丝点击