Android视图的绘制流程(下)——View的Layout与Draw过程

综述

  在上篇文章中Android视图的绘制流程(上)——View的测量对View的Measure过程进行了详细的说明。对于在View的绘制的整个过程中,在对View的大小进行测量以后,便开始确定View的位置并且将其绘制到屏幕上。也就是View的Layout与Draw过程。那么就来看一下是如何实现这两个过程的。

View的Layout过程

  上文提到View的绘制流程是从ViewRoot的performTraversals方法开始,那么在View完成测量以后,在performTraversals方法中对performLayout进行调用。在performLayout中可以找到下面这行代码。

host.layout(0, 0, host.getMeasuredWidth(),host.getMeasuredHeight())

  上面这行代码中的host指的就是DecorView,对于这个DecorView我们都知道它是一个继承自FrameLayout的ViewGroup。这个layout方法也就是ViewGroup中的layout方法。下面就来看一下ViewGroup中的这个layout方法。

@Override
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;
    }
}

  从ViewGroup的layout方法我们可以看出它是一个final类型的,也就是说在ViewGroup中的layout方法是不能被子类重写的。ViewGroup中的layout方法中又调用父类的layout方法,也就是View的layout方法。下面就来看一下View的layout方法。

@SuppressWarnings({"unchecked"})
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);
        ......
    }

    ......
}

  对于setOpticalFrame实质上也是调用setFrame方法,而setFrame的作用是将View的位置分别保存到mLeft,mTop,mBottom,mRight变量当中。之后在判断是否需要重新布局,如果需要重新布局的话,便调用onLayout方法。

  其实在View的Layout过程当中,在View的layout方法是确定View的自身位置,而在View的onLayout方法中则是确定View子元素的位置。所以在这可以看到对于View的onLayout是一个空方法,没有完成任何事情。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

  而ViewGroup的onLayout方法则是一个抽象方法,通过具体的ViewGroup实现类来完成对子元素的Layout过程。

@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

  下面依然通过一个具体的ViewGroup,来看一下FrameLayout的onLayout方法实现过程,对于FrameLayout的onLayout方法的实现是非常简单的,所以就以FrameLayout为例进行说明。下面来看一下FrameLayout的onLayout方法。

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

  在FrameLayout的onLayout方法中只是调用了layoutChildren方法,从这个方法名便可以看出它的功能就是为FrameLayout的子元素进行布局。下面就来看一下这个layoutChildren方法。

void layoutChildren(int left, int top, int right, int bottom,
                              boolean forceLeftGravity) {
    final int count = getChildCount();

    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();

    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            int childLeft;
            int childTop;

            int gravity = lp.gravity;
            if (gravity == -1) {
                gravity = DEFAULT_CHILD_GRAVITY;
            }

            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                    lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:
                    if (!forceLeftGravity) {
                        childLeft = parentRight - width - lp.rightMargin;
                        break;
                    }
                case Gravity.LEFT:
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }

            switch (verticalGravity) {
                case Gravity.TOP:
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                    lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }

            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

  对于这段代码的逻辑也很简单。通过遍历FrameLayout内所有的子元素,然后获取到View测量后的宽和高,在根据子View的Gravity属性来决定子View在父控件中四个顶点的位置。最后调用子View的layout方法来完成View的整个测量过程。

View的Draw过程

  在通过ViewRoot的performTraversals方法完成对View树的整个布局以后,下面便开始将View绘制到手机屏幕上。对于View的Draw过程在ViewRoot的performTraversals方法中通过调用performDraw方法来完成的。在performDraw方法中最终会通过创建一个Canvas对象,并调用View的draw方法,来完成View的绘制。

@CallSuper
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;

    /*
     * 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)
     */

    // Step 1, draw the background, if needed
    int saveCount;

    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // we‘re done...
        return;
    }

   ......
}

  在这里从宏观上来对Draw过程进行一下分析。在注释中可以看出对于View的绘制过程分为六步进行的。其中第二步和第五步一般很少用到,可以忽略。剩下几步则为:

  1. 绘制背景 drawBackground(canvas)
  2. 绘制自身 onDraw(canvas)
  3. 绘制 children dispatchDraw(canvas)
  4. 绘制装饰 onDrawForeground(canvas)

      对于子View的绘制传递是通过dispatchDraw来进行的,在View中的dispatchDraw方法是由ViewGroup来实现的,并且遍历调用所有子元素的draw方法,完成整个View树的绘制过程。

总结

  对于View的绘制流程,总共分为三大步。分别是View的测量,布局与绘制。首先通过ViewRoot,对View树根节点进行操作。依次向下遍历,完成它们的Measure,Layout,Draw过程。从而使View展现在手机屏幕上。

时间: 2024-12-10 10:31:16

Android视图的绘制流程(下)——View的Layout与Draw过程的相关文章

Android View体系(八)从源码解析View的layout和draw流程

相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源码解析Scroller Android View体系(五)从源码解析View的事件分发机制 Android View体系(六)从源码解析Activity的构成 Android View体系(七)从源码解析View的measure流程 前言 上一篇文章我们讲了View的measure的流程,接下来我们

Android View体系(八)从源代码解析View的layout和draw流程

相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源代码解析Scroller Android View体系(五)从源代码解析View的事件分发机制 Android View体系(六)从源代码解析Activity的构成 Android View体系(七)从源代码解析View的measure流程 前言 上一篇文章我们讲了View的measure的流程.接

128、View 绘制流程 &amp; 自定义View

记清楚函数调用的顺序才能准确地进行调用. 根据调用链,可将整个绘制过程分为三部分:Measure - Layout - Draw Measure 过程 1. 测量过程由上至下,在measure过程的最后,每个视图将存储自己的尺寸大小和测量规格. 2. measure过程会为一个View及其所有子节点的mMeasureWidth和mMeasuredHeight变量赋值, 该值可以通过getMeasuredWidth和getMeasuredHeight方法获得. 3. measure过程的核心方法:

Android View框架总结(六)View布局流程之Draw过程

转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52236145 View的Draw时序图 ViewRootImpl.performTraversals过程 ViewRootImpl.performDraw过程 View.draw方法 View.dispatchDraw过程 LinearLayout的onDraw过程 View的Draw时序图 前面几篇通过对View树的measure和layout过程分析事,接下来将结合前两步得到的测

Android学习笔记(四)View和Layout

http://www.cnblogs.com/cxcco/archive/2011/12/09/2282701.html 二.正文 所谓人靠衣装马靠鞍,一个要想吸引用户的应用程序,光靠功能是没有多大作用的,漂亮丰富的UI也是极其重要的.如何才能创建一个漂亮.可操作性强的应用程序? 1. View介绍 在Android中,所有的可视化组件都是继承自View类,用户通过View和ViewGroup或者扩展自他们的类来构建用户界面.一个View对象处理它自己的测度.布局.绘图.焦点改变.滚动.键/手势

Android自定义控件 ----- 基本绘制流程,简单控件的实现

一.自定义控件(一) --- 自定义属性TextView 1,定义属性,制作attrs.xml文件: 属性值: string,color,attr,array,bool,declare-styleable,dimen,drawable,eat-comment,fraction, integer,integer-array,item,plurals,string-array,style 属性取值范围: string,color,demension,integer,enum,reference,fl

Android视图View绘制流程与源码分析(全)

来源:[工匠若水 http://blog.csdn.net/yanbober] 1 背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原理,记不记得最终分析结果就是下面的关系: 看见没有,如上图中id为content的内容就是整个View树的结构,所以对每个具体View对象的操作,其实就是个递归的实现. 前面<Android触摸屏事件派发机制详解与源码分析一(

Android中View绘制流程以及invalidate()等相关方法分析

前言: 本文是我读<Android内核剖析>第13章----View工作原理总结而成的,在此膜拜下作者 .同时真挚地向渴望了解 Android 框架层的网友,推荐这本书,希望你们能够在Android开发里学到更多的知识 . 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为 根据之前设置的状态,判断是否需要重新计算视图大小(measure).是否重新需要安置视图的位置(layout).以及是否需要重绘 (d

Android View的绘制流程

View 绘制机制 1. View 树的绘图流程 当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 draw.整个 View 树的绘图流程在ViewRoot.java类的performTraversals()函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure).是否需要重新安置视图的位置(layout).以及是否需要重绘(draw),流程图如下: Vi