美文网首页
View的工作原理

View的工作原理

作者: ae12 | 来源:发表于2018-11-26 19:09 被阅读6次

    View工作原理包括其三大流程:onMeasure() -->onLayout--->onDraw()
    测量、布局、绘制。
    View的常用回调方法onAttach() onVisibilityChanged() onDetach()
    如果View是可以滑动的,还需要解决View的滑动冲突
    View 的自定义实现方式
    1.继承View/viewGroup
    2.继承现有的控件

     ### 初识ViewRoot 和DecorView    
    

    ViewRoot对应于ViewRootImpl是最终执行onMeasure() --onLayout() --onDraw()的类,
    连接了WinodowManager和DecorView;
    DecorView作为顶级View,其实是一个FrameLayout,通常包含一个Vertical的LinearLayout,里面是 titlebar +contentView,我们用的setContentView()就是set到这个content里。
    所有的View事件都是经过Decorview然后传到我们的View.

    Measure 过程

    view的measure:

       ### OnMeasure
    

    View系统绘制从ViewRoot.performTraversals() 开始,在其内部调用
    View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。measure()方法接收两个参数,

    接下来我们看下View的measure()方法里面的代码吧,如下所示:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {
            mPrivateFlags &= ~MEASURED_DIMENSION_SET;
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
            }
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }
            mPrivateFlags |= LAYOUT_REQUIRED;
        }
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    

    如上面👆代码可以看到:measure()这个方法是final的,因此我们无法在子类中去重写这个方法,说明Android是不允许我们改变View的measure框架的。然后调用了onMeasure()方法,这里才是真正去测量并设置View大小的地方,默认会调用getDefaultSize()方法来获取视图的大小,如下所示:

     ### onMeasure()方法如下:
    
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    
    
    ### getDefaultSize()方法如下:
    
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
    

    这上上面的代码中传入的measureSpec是measure()方法中传来的,先解析measureSpec中的specSize specMode,
    如果是AT_MOST EXACTLY,就返回specSize(),这是系统默认的。如果是UNSPECIFIED,测量大小就是getSuggestedMinimumWidth() ;

    getSuggestedMinimumWidth() ;

    protected int getSuggestedMinimumWidth() {
        //如果没有给View设置背景,那么就返回View本身的最小宽度mMinWidth
        //如果给View设置了背景,那么就取View本身最小宽度mMinWidth和背景的最小宽度的最大值
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    

    mBackground.getMinimumWidth()返回的是Drawable 的原始宽度,ShapDrawable无原始宽度,BitmapDrawable有原始宽度。即:无背景,返回minWidth(),不设置为0;有背景返回max(mMinWidth,背景最小宽度);
    之后在setMeasuredDimension()中设置测量的大小,这样一次measure过程结束。

    viewgroup的measure过程

    viewgroup除了measure本身,还要遍历子view,调用每个子View的measure()
    ViewGroup 是个抽象类,它没有ovveridde View的onMeasure(),
    但是定义了measureChildren()来测量子视图大小。

    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
    
    

    遍历所有子视图,然后逐一调用measureChild() 方法测量子视图。

    ### measureChild() 方法below :
    
    
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
    

    根据布局中的MATCH_PARENT、WRAP_CONTENT通过getChildMeasureSpec()计算得到 child的MeasureSpec,然后传入measure()中,之后,就是前面的measure()流程。

    可以重写 onMeasure()方法:

    public class MyView extends View {
     
        ......
        
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(200, 200);
        }
    

    主要代码:

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        。。。
    
        
                    // Determine how big this child would like to be. If this or
                    // previous children have given a weight, then we allow it to
                    // use all available space (and we will shrink things later
                    // if needed).
                    final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                    measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                            heightMeasureSpec, usedHeight);
    
                  
         ....
        
    
          setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                    heightSizeAndState);
    
        }
    

    measureChildBeforeLayout()内部调用各个子View的measure(),mTotalLength存储Linearlayout在竖直方向的高度,每测量一个view,mTotalLength增加,增加部分是子元素的高度以及竖直的margin;当子元素测量完后,linearlayout进入自己的测量
    setMeasuredDimension();

    这样不管xml布局怎样设置,view的大小都是200 *200 ;
    注意:只有在setMeasuredDimension之后,才可以getMeasuredWidth()来获取测量后的宽高。

    ViewGroup没有实现具体的测量方法,因为ViewGroup是抽象类,其测量过程的onMeasure()x需要各个子类具体去实现。为什么需要具体实现?因为不同的ViewGroup子类如RelativeLayout、LinearLayout的特性不一致,所以需要具体实现。
    下面以LinearLayout 的onMeasure()为例子,来分析:

     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (mOrientation == VERTICAL) {
                measureVertical(widthMeasureSpec, heightMeasureSpec);
            } else {
                measureHorizontal(widthMeasureSpec, heightMeasureSpec);
            }
        }
    

    视图大小由父视图、布局、视图本身决定,父视图提供参考,布局

    onLayout() :

       public void layout(int l, int t, int r, int b) {
            if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
                onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
    
            int oldL = mLeft;
            int oldT = mTop;
            int oldB = mBottom;
            int oldR = mRight;
    
            boolean changed = isLayoutModeOptical(mParent) ?
                    setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    
            if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
                onLayout(changed, l, t, r, b);
    
                if (shouldDrawRoundScrollbar()) {
                    if(mRoundScrollbarRenderer == null) {
                        mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                    }
                } else {
                    mRoundScrollbarRenderer = null;
                }
    
                mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
    
                ListenerInfo li = mListenerInfo;
                if (li != null && li.mOnLayoutChangeListeners != null) {
                    ArrayList<OnLayoutChangeListener> listenersCopy =
                            (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                    int numListeners = listenersCopy.size();
                    for (int i = 0; i < numListeners; ++i) {
                        listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                    }
                }
            }
    
            mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
            mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    
            if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
                mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
                notifyEnterOrExitForAutoFillIfNeeded(true);
            }
        }
    
    

    这里判断view的位置和尺寸是否改变了,

    //如果isLayoutModeOptical()返回true,那么就会执行setOpticalFrame()方法,
    //否则会执行setFrame()方法。并且setOpticalFrame()内部会调用setFrame(),
    //所以无论如何都会执行setFrame()方法。
    //setFrame()方法会将View新的left、top、right、bottom存储到View的成员变量中
    //并且返回一个boolean值,如果返回true表示View的位置或尺寸发生了变化,
    //否则表示未发生变化
    boolean changed = isLayoutModeOptical(mParent) ?
    setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    如果

    相关文章

      网友评论

          本文标题:View的工作原理

          本文链接:https://www.haomeiwen.com/subject/gfllqqtx.html