言简意赅的View绘制流程

   日期:2020-05-19     浏览:98    评论:0    
核心提示:一、基本概念:1、Activity、Window、PhoneWindow、DecorView之间的关系:Activity:很熟悉了,我们所能见到页面,不过多阐述;Window:每个Activity都包含一个Window,也可以说每个Activity都包含一个Window的对象。Window是一个抽象基类,是Activity和View的交互接口,且只有一个实现类PhoneWindow。我们可以将其理解为一个载体,各种View都在这个载体上显示。public class Activity extends

一、基本概念:

1、Activity、Window、PhoneWindow、DecorView之间的关系:

Activity:很熟悉了,我们所能见到页面,不过多阐述;
Window:每个Activity都包含一个Window,也可以说每个Activity都包含一个Window的对象。Window是一个抽象基类,是Activity和View的交互接口,且只有一个实现类PhoneWindow。我们可以将其理解为一个载体,各种View都在这个载体上显示。

public class Activity extends ContextThemeWrappe{
  private Window mWindow;
}

PhoneWindow:继承于Window类,是Window的具体实现。在PhoneWindow中持有着一个很重要的View对象,DecorView。

public class PhoneWindow extends Window{
  private DecorView mDecor; 
}

DecorView:是所有窗口的根View,继承于FrameLayout,并对其进行扩展修饰,比如添加TitleBar等。

2、MeasureSpc:

① 在 Google 官方文档中是这么定义:

A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode.

大概意思是:MeasureSpec 封装了从父View 传递给到子View的布局需求,每个MeasureSpec代表了view的宽度或高度的测量要求,即size(大小)和mode(模式)。
② MeasureSpc是一个32位的int值,高2位代表SpecMode(测量模式),低30位代表了SpecSize(测量大小)。
③ SpecMode分为三类:
· unSpecified:父容器不对子View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。(可忽略)
· Exactly:父容器已经测量出子View的大小。对应是view的LayoutParams的match_parent或者具体数值。
· At_most:父容器指定了一个可用大小的SpecSize,view的大小不能够大于这个值,对应wrap_content。
④ 重点:View的MeasureSpec,由父 view 的MeasureSpec加上子 View 的自己的 LayoutParams,通过相应的规则转化,共同决定。
从父View的角度分析MeasureSpec的转化规则:

  • 当父View的SpecMode是Exactly的时候,即父View的大小是确定的种情况:
    ①若子View宽/高为match_parent,则子View的SpecMode也是Exactly模式,且其大小是父容器的剩余空间。
    ②若子View宽/高为wrap_parent,则子View的SpecMode为At_most模式,且其大小不超过父容器的剩余空间。
    ③若子View宽/高为具体数值,则子View的SpecMode为Exactly模式,大小就是该具体数值。
  • 当父View的SpecMode是At_most的时候,即父View的大小是不确定的情况:
    ①若子View宽/高为match_parent,则子View的SpecMode也是At_most模式,且其大小不超过父容器的剩余空间。
    ②若子View宽/高为wrap_parent,则子View的SpecMode为At_most模式,且其大小不超过父容器的剩余空间。
    ③若子View宽/高为固定宽高,则子View的SpecMode为Exactly模式,大小就是该固定宽高。

从子View的角度分析MeasureSpec的转化规则:

  • 当子View的宽/高采用固定宽高,无论父容器的SpecMode是什么,子View的SpecMode都是Exactly,其大小遵循LayoutParams的大小。
  • 当子VIew的宽/高采用match_parent:
    ①若父容器的SpecMode为Exactly,那么子View的SpecMode也为Exactly,且其大小是父容器的剩余空间。
    ②若父容器的SpecMode为At_most,那么子View的SpecMode也为At_most,且其大小是父容器的剩余空间。
  • 当子View的宽/高采用wrap_content,无论父容器是什么模式,子View的模式总是At_most,且大小不能超过父容器的剩余空间。

二、流程概述 :

view的绘制流程概括的描述为三部曲:measure -> layout -> draw

三、流程详述 :

1、ViewRootImpl (绘制入口):

Window的添加过程需要通过WindowManager的addView来实现。在addView时会创建一个ViewRootImpl对象,然后通过该对象可的setView完成Window的添加,在setView中调用requestLayout()完成异步刷新请求,在requestLayout中调用performTraversals来完场view的绘制。
总结:WindowManagerImpl -> addView -> new ViewRootImpl -> setView -> requestLayout -> performTraversals
绘制流程三部曲的核心入口就在 ViewRootImpl 类的 performTraversals() 方法中。

private void performTraversals() {
    ......
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ......
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    ......
    mView.draw(canvas);
    ......
 }

2、measure(测量):

作用:measure 主要功能就是测量设置 View 的大小。
该过程由view的measure方法来完成,该方法是 final 类型,子类不能覆盖,在measure方法中会调用 onMeasure()方法,我们也可以复写 onMeasure()方法去测量设置 View 的大小。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        .....
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        .....
}

onMeasure(这段代码就是measure过程的核心代码),接下来对onMeasure重点刨析:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(
            getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
        );
}

① onMeasure#getSuggestedMinimumWidth()

protected int getSuggestedMinimumWidth() {
        //返回View的建议最小宽度值,即xml布局中用设置的属性minWidth或背景大小。
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

同理可得 onMeasure#getSuggestedMinimumHeight()。

② onMeasure#getDefaultSize()
作用:根据View的建议最小值,结合父View传递的measureSpec,返回SpecSize,而这个值基本就是view测量后的大小

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        //获取父View传递过来的模式
        int specMode = MeasureSpec.getMode(measureSpec);
        //获取父View传递过来的大小
        int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;//View的大小父View未定,设置为建议最小值 
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
}

getDefaultSize 的逻辑跟我们之前分析的 MeasureSpec 转化规则非常相似。就是根据specMode设置大小。

③ onMeasure#setMeasuredDimension()
作用:保存测量好的宽跟高
注意:在onMeasure中必须调用这个方法,不然就会抛出 IllegalStateException 异常。
至此,View的measure的过程已经介绍完毕。
注: ViewGroup(即父View)的measure过程:
ViewGroup除了完成自己的measure的过程以外,还要遍历取调用子View的measure方法,各个子View再去递归执行这个过程,从而达到了测量目的。和View不同的是,ViewGroup是个抽象类,他的测量过程并没有像View一样,在onMeasure方法中做统一处理,而是由其子类(比如,LinearLayout、RelativeLayout等)各自实现或其内部方法measureChildren方法实现。在measureChildren方法中,遍历子View,调用measureChild方法,测量每个子View。在measureChild中,首先获取子View的LayoutParams,通过该值获取子View的WidthMeasureSpec和HeightMeasureSpec,最后调用每个子View的measure方法,将WidthMeasureSpec和HeightMeasureSpec作为参数传入,进行测量,这样又回到了上述View的绘制流程中。对于LinearLayout和RelativeLayout等ViewGroup的子类,其实现流程大致相同,也是遍历子View,然后调用各自内部相应的方法,进而调用子View的measure方法。

3、layout(布局):

作用:确定ViewGroup和子View的位置
对View进行布局排版,要从ViewGroup说起,ViewGroup的layout方法:

public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

LayoutTransition是用于处理ViewGroup增加和删除子视图的动画效果,也就是说如果当前ViewGroup未添加LayoutTransition动画,或者LayoutTransition动画此刻并未运行,那么调用super.layout(l, t, r, b),即调用View的layout方法,否则将mLayoutCalledWhileSuppressed设置为true,等待动画完成时再调用requestLayout()。

public void layout(int l, int t, int r, int b) {
    ...
    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);
            }
        }
    }
    ...
}

首先通过调用setFrame方法来设定四个顶点的位置,接下来调用onLayout方法,用于确定子View的位置。然而在View和ViewGroup中,均没有真正实现onLayout,因为onLayout的实现在ViewGroup的各子类中,如下是LinearLayout和RelativeLayout的onLayout代码:

//LinearLayout的onLayout方法
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (mOrientation == VERTICAL) {
        layoutVertical(l, t, r, b);
    } else {
        layoutHorizontal(l, t, r, b);
    }
}//layoutVertical
void layoutVertical(int left, int top, int right, int bottom) {
    ...
    final int count = getVirtualChildCount();
    ...
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();final LinearLayout.LayoutParams lp =
                (LinearLayout.LayoutParams) child.getLayoutParams();int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }
            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                        + lp.leftMargin - lp.rightMargin;
                    break;case Gravity.RIGHT:
                    childLeft = childRight - childWidth - lp.rightMargin;
                    break;case Gravity.LEFT:
                default:
                    childLeft = paddingLeft + lp.leftMargin;
                    break;
            }if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }
​
            childTop += lp.topMargin;
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                          childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
​
            i += getChildrenSkipCount(child, i);
        }
    }
}//setChildFrame
private void setChildFrame(View child, int left, int top, int width, int height) {
    child.layout(left, top, left + width, top + height);
}

// RelativeLayout的onLayout方法
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    // The layout has actually already been performed and the positions
    // cached. Apply the cached values to the children.
    final int count = getChildCount();for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            RelativeLayout.LayoutParams st =
                (RelativeLayout.LayoutParams) child.getLayoutParams();
            child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
        }
    }
}

ViewGroup子类的方法中原理大致相似,都是遍历所有子View,最后调用子View的layout方法来指定子View的位置。
总结:View的layout过程较measure过程相对简单,首先ViewGroup的layout的方法会被调用,通过setFrame来确定父View的位置,然后调用onLayout来确定子View的布局,onLayout的具体实现在ViewGroup的子类中,遍历所有子View,最终调用子View的layout方法来确定自己的位置,这样一层层的传递下去,完成整个View树的layout过程。

4、draw(绘制):

Draw过程就相对更简单了,其作用是将View绘制到屏幕上面,其主要的绘制流程在如下源码中注释:

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
        (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
    
    int saveCount;
    if (!dirtyOpaque) {
    	// 绘制背景,如果需要。
        drawBackground(canvas);
    }final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // 绘制自己
        if (!dirtyOpaque) onDraw(canvas);// 绘制子View
        dispatchDraw(canvas);drawAutofilledHighlight(canvas);if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }// 绘制装饰
        onDrawForeground(canvas);drawDefaultFocusHighlight(canvas);if (debugDraw()) {
            debugDrawFocus(canvas);
        }// we're done...
        return;
    }
    ...
    }

主要的绘制步骤总结如下:
1.绘制背景 (drawBackground(canvas))
2.绘制自己 (onDraw(canvas);自定义View的绘制在这里进行,该方法是一个空方法,具体实现在View的继承类中,自己复写实现内容绘制)
3.绘制子View (dispatchDraw(canvas))
4.绘制装饰 (onDrawForeground(canvas); 如ScrollBar等)

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服