美文网首页
Android View工作原理

Android View工作原理

作者: 小白咸菜 | 来源:发表于2019-05-23 11:38 被阅读0次

    前言

    在Android知识体系中,View扮演了很重要的角色;它是Android在视觉上的呈现,Android本身提供了一套GUI库,但是我们的需求不止于系统自带的GUI,因此我们还需要自定义View。而自定义View过程中我们势必要对View的底层工作原理有所了解。这篇文章就记录一下View的测量、布局、绘制三大流程。

    1.一些必要的基本概念

    1.1 ViewRoot和DecorView

    ViewRoot对应于ViewRootImpl类,连接WindowManagerDecorView的纽带;View的三大流程基于RootView来完成的;View的绘制流程是从ViewRoot的performTraversals方法开始的。

    DecorView顶级View,内部一般为一个竖直方向的LinearLayout,分为2部分:titlebar以及android.R.id.content的FrameLayout。

    结构图如下:

    在这里插入图片描述
    1.2 MeasureSpec

    字面意思为“测量规格”;

    它代表一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(某种测量模式下的规格大小)。

    SpecMode一共有3类:

    1. EXACTLY:match_parent和具体数值,父容器已经检测出View的精确大小

    2. AT_MOST:父容器制定一个可用大小,View的大小不能大于它

    3. UNSPECIFIED: 父容器不对View有任何限制

    一般来说,View的MeasureSpec由本身的LayoutParams与父容器的MeasureSpec共同决定。DecorView的MeasureSpec由LayoutParams决定。父容器通过measure传递MeasureSpec给子View.

    引用刚哥的《Android 开发艺术探索》的图:这张图涵括了View Measure中最核心的过程。

    在这里插入图片描述

    它在View的底层原理中起到的作用下面的篇幅在做具体的介绍。源码如下:所在包import android.view.View.MeasureSpec;

    
    /**
    
        * 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. There are three possible
    
        * modes:
    
        * <dl>
    
        * <dt>UNSPECIFIED</dt>
    
        * <dd>
    
        * The parent has not imposed any constraint on the child. It can be whatever size
    
        * it wants.
    
        * </dd>
    
        *
    
        * <dt>EXACTLY</dt>
    
        * <dd>
    
        * The parent has determined an exact size for the child. The child is going to be
    
        * given those bounds regardless of how big it wants to be.
    
        * </dd>
    
        *
    
        * <dt>AT_MOST</dt>
    
        * <dd>
    
        * The child can be as large as it wants up to the specified size.
    
        * </dd>
    
        * </dl>
    
        *
    
        * MeasureSpecs are implemented as ints to reduce object allocation. This class
    
        * is provided to pack and unpack the &lt;size, mode&gt; tuple into the int.
    
        */
    
        public static class MeasureSpec {
    
            private static final int MODE_SHIFT = 30;
    
            private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    
            /** @hide */
    
            @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
    
            @Retention(RetentionPolicy.SOURCE)
    
            public @interface MeasureSpecMode {}
    
            public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
            public static final int EXACTLY    = 1 << MODE_SHIFT;
    
            public static final int AT_MOST    = 2 << MODE_SHIFT;
    
    
    
            public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
    
                                              @MeasureSpecMode int mode) {
    
                if (sUseBrokenMakeMeasureSpec) {
    
                    return size + mode;
    
                } else {
    
                    return (size & ~MODE_MASK) | (mode & MODE_MASK);
    
                }
    
            }
    
            public static int makeSafeMeasureSpec(int size, int mode) {
    
                if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
    
                    return 0;
    
                }
    
                return makeMeasureSpec(size, mode);
    
            }
    
            public static int getMode(int measureSpec) {
    
                //noinspection ResourceType
    
                return (measureSpec & MODE_MASK);
    
            }
    
            public static int getSize(int measureSpec) {
    
                return (measureSpec & ~MODE_MASK);
    
            }
    
            static int adjust(int measureSpec, int delta) {
    
                final int mode = getMode(measureSpec);
    
                int size = getSize(measureSpec);
    
                if (mode == UNSPECIFIED) {
    
                    // No need to adjust size for UNSPECIFIED mode.
    
                    return makeMeasureSpec(size, UNSPECIFIED);
    
                }
    
                size += delta;
    
                if (size < 0) {
    
                    Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
    
                            ") spec: " + toString(measureSpec) + " delta: " + delta);
    
                    size = 0;
    
                }
    
                return makeMeasureSpec(size, mode);
    
            }
    
            public static String toString(int measureSpec) {
    
                int mode = getMode(measureSpec);
    
                int size = getSize(measureSpec);
    
                StringBuilder sb = new StringBuilder("MeasureSpec: ");
    
                if (mode == UNSPECIFIED)
    
                    sb.append("UNSPECIFIED ");
    
                else if (mode == EXACTLY)
    
                    sb.append("EXACTLY ");
    
                else if (mode == AT_MOST)
    
                    sb.append("AT_MOST ");
    
                else
    
                    sb.append(mode).append(" ");
    
                sb.append(size);
    
                return sb.toString();
    
            }
    
        }
    
    

    2.View的工作原理

    上面介绍了View底层工作原理所必备的一些知识后,现在重点介绍View的工作原理:

    View三大流程的入口(通过Log可以得知):是从onResume()之后开始的。跟踪ActivityThread的源码,在handleResumeActivity()中开始.

    2.1 handleResumeActivity()
    
    @Override
    
        public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
    
                String reason) {
    
            ......
    
            if (r.window == null && !a.mFinished && willBeVisible) {
    
                r.window = r.activity.getWindow();
    
                View decor = r.window.getDecorView();
    
                decor.setVisibility(View.INVISIBLE);
    
                ViewManager wm = a.getWindowManager();
    
                WindowManager.LayoutParams l = r.window.getAttributes();
    
                a.mDecor = decor;
    
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
    
                l.softInputMode |= forwardBit;
    
                if (r.mPreserveWindow) {
    
                    a.mWindowAdded = true;
    
                    r.mPreserveWindow = false;
    
                    // Normally the ViewRoot sets up callbacks with the Activity
    
                    // in addView->ViewRootImpl#setView. If we are instead reusing
    
                    // the decor view we have to notify the view root that the
    
                    // callbacks may have changed.
    
                    ViewRootImpl impl = decor.getViewRootImpl();
    
                    if (impl != null) {
    
                        impl.notifyChildRebuilt();
    
                    }
    
                }
    
                if (a.mVisibleFromClient) {
    
                    if (!a.mWindowAdded) {
    
                        a.mWindowAdded = true;
    
                        wm.addView(decor, l);
    
                    } else {
    
                        // The activity will get a callback for this {@link LayoutParams} change
    
                        // earlier. However, at that time the decor will not be set (this is set
    
                        // in this method), so no action will be taken. This call ensures the
    
                        // callback occurs with the decor set.
    
                        a.onWindowAttributesChanged(l);
    
                    }
    
                }
    
              ......
    
        }
    
    

    ,注意注释 // in addView->ViewRootImpl#setView. If we are instead reusing最终调用ViewRootImpl.setView();

    2.2 ViewRootImpl.setView()
    
    /**
    
        * We have one child
    
        */
    
        public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    
            ......
    
            requestLayout();
    
            ......
    
        }
    
    

    进入requestLayout()方法。

    2.3 ViewRootImpl.requestLayout()
    
    @Override
    
        public void requestLayout() {
    
            if (!mHandlingLayoutInLayoutRequest) {
    
                checkThread();
    
                mLayoutRequested = true;
    
                scheduleTraversals();
    
            }
    
        }
    
    
    2.4 ViewRootImpl.performTraversals()
    
    @Override
    
        public void performTraversals() {
    
          ......
    
          performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
          ......
    
          performLayout(lp, mWidth, mHeight);
    
          ......
    
          performDraw();
    
          ......
    
        }
    
    

    核心方法:View的工作主要流程就在此方法中。

    归纳上方的流程为下图:

    在这里插入图片描述
    • measure 测量,决定了测量后的宽高

    • layout 布局,确定四个定点坐标以及View的实际宽高

    • draw 绘制,决定View的显示

    依次调用performMeasure、performLayout、performDraw方法,完成顶级View的measure、layout、draw;而在measure、layout、draw又会调用oMeasure、onLayout、onDraw对其子元素重复递归上过程,直到view不为ViewGroup时。

    3.View的measure过程

    1. View的measure

    具体流程:

    在这里插入图片描述

    一系列的方法会设置View宽高的测量值。其中getSuggestedMinimumWidth()会根据是存在背景来返回最小宽度。

    
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    
        }
    
    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;
    
        }
    
    protected int getSuggestedMinimumWidth() {
    
            return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    
        }
    
    

    从getDefault方法中可得结论:

    直接继承View的自定义控件需重写onMeasure方法并设置wrap_content时的自身大小,否则使用wrap_content就相当于使用match_content。

    1. ViewGroup的measure

    而ViewGroup需要完成本身的measure过程外,还要遍历所有子元素的measure方法,递归执行。本身未重写onMeasure()方法,提供measureChildren()方法遍历测量View树。具体继承自它的类重写onMeasure()对子View进行测量。

    首先要确定DecorView的MeasureSpec用来传递给子View进行生成MeasureSpec。DecorView通过getRootMeasureSpec确定本身测量模式以及大小。其中windowSize就是屏幕的宽高。

    
        private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    
            int measureSpec;
    
            switch (rootDimension) {
    
            case ViewGroup.LayoutParams.MATCH_PARENT:
    
                // Window can't resize. Force root view to be windowSize.
    
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
    
                break;
    
            case ViewGroup.LayoutParams.WRAP_CONTENT:
    
                // Window can resize. Set max size for root view.
    
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
    
                break;
    
            default:
    
                // Window wants to be an exact size. Force root view to be that size.
    
                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
    
                break;
    
            }
    
            return measureSpec;
    
        }
    
    

    其次,通过measureChildren(int widthMeasureSpec, int heightMeasureSpec)遍历测量所有的View树。

    
    // 遍历测量View树
    
    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);
    
                }
    
            }
    
        }
    
    // 根据LayoutParams 以及 父View传递过来的MeasureSpec 创建自身的测量模式/规格
    
    protected void measureChild(View child, int parentWidthMeasureSpec,
    
                int parentHeightMeasureSpec) {
    
            //  自身布局参数   
    
            final LayoutParams lp = child.getLayoutParams();
    
            // 获取当前所需测量child的宽度的测量模式
    
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
    
                    mPaddingLeft + mPaddingRight, lp.width);
    
            // 获取当前所需测量child的高度的测量模式     
    
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
    
                    mPaddingTop + mPaddingBottom, lp.height);
    
    // 最终调用measure,判断是View还是ViewGroup
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
        }
    
    

    其中最重要的源码部分如下:此部分即可以归纳为上面引入的图片。

    
    // spec 父Spec padding值  childDimension为布局文件中设置的参数
    
      public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    
      // 父specMode
    
            int specMode = MeasureSpec.getMode(spec);
    
            // 父specSize
    
            int specSize = MeasureSpec.getSize(spec);
    
    // 父实际可用的空间
    
            int size = Math.max(0, specSize - padding);
    
    // 最终size
    
            int resultSize = 0;
    
            // 最终模式
    
            int resultMode = 0;
    
    // 结合父测量模式以及自身LayoutParams来创建child的测量模式
    
            switch (specMode) {
    
            // Parent has imposed an exact size on us
    
            case MeasureSpec.EXACTLY:
    
                if (childDimension >= 0) {
    
                    resultSize = childDimension;
    
                    resultMode = MeasureSpec.EXACTLY;
    
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
    
                    // Child wants to be our size. So be it.
    
                    resultSize = size;
    
                    resultMode = MeasureSpec.EXACTLY;
    
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
    
                    // Child wants to determine its own size. It can't be
    
                    // bigger than us.
    
                    resultSize = size;
    
                    resultMode = MeasureSpec.AT_MOST;
    
                }
    
                break;
    
            // Parent has imposed a maximum size on us
    
            case MeasureSpec.AT_MOST:
    
                if (childDimension >= 0) {
    
                    // Child wants a specific size... so be it
    
                    resultSize = childDimension;
    
                    resultMode = MeasureSpec.EXACTLY;
    
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
    
                    // Child wants to be our size, but our size is not fixed.
    
                    // Constrain child to not be bigger than us.
    
                    resultSize = size;
    
                    resultMode = MeasureSpec.AT_MOST;
    
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
    
                    // Child wants to determine its own size. It can't be
    
                    // bigger than us.
    
                    resultSize = size;
    
                    resultMode = MeasureSpec.AT_MOST;
    
                }
    
                break;
    
            // Parent asked to see how big we want to be
    
            case MeasureSpec.UNSPECIFIED:
    
                if (childDimension >= 0) {
    
                    // Child wants a specific size... let him have it
    
                    resultSize = childDimension;
    
                    resultMode = MeasureSpec.EXACTLY;
    
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
    
                    // Child wants to be our size... find out how big it should
    
                    // be
    
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
    
                    resultMode = MeasureSpec.UNSPECIFIED;
    
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
    
                    // Child wants to determine its own size.... find out how
    
                    // big it should be
    
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
    
                    resultMode = MeasureSpec.UNSPECIFIED;
    
                }
    
                break;
    
            }
    
            //noinspection ResourceType
    
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    
        }
    
    

    总结:

    • 如果View的宽高是固定值,无论父MeasureSpec为何值,View的测量模式都为EXACTLY,且specSize都为实际值

    • 如果View的宽高是wrap_content,无论父MeasureSpec为何值(EXACTLY/AT_MOST),View的测量模式都为AT_MOST,且specSize都不笨办法超过父实际的size

    • 如果View的宽高是match_parent,父测量模式为EXACTLY,子View的测量模式也为EXACTLY;父测量模式为AT_MOST,子View的测量模式也为AT_MOST

    到此整个View的Measure过程就结束了,测量完成后可通过getMeasuredWidth/Height()获取到View的测量宽高。整个流程的流程图如下(省略了好多细节,大体上走了一遍流程):

    在这里插入图片描述

    4.View的layout过程

    Layout的作用是确定子元素的位置,layout方法确定本身所在位置,onLayout确定所有子元素的位置。跟踪ViewRootImpl中的performLayout方法。

    
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
    
                int desiredWindowHeight) {
    
    
    
    ......
    
    // host 即为DecorView
    
            final View host = mView;
    
            if (host == null) {
    
                return;
    
            }
    
      ......
    
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    
        ......
    
    
    
        }
    
    

    4.1 View中的layout方法

    首先初始化LTRB,其中l,t,r,b分别为View的四个顶点的位置。接着调用onLayout确定子元素的位置。onLayout与onMeasure相似,具体的实现在具体的View中重写即可。onLayout中遍历调用setChildFrame(),内部调用的是 child.layout(left, top, left + width, top + height);通过递归完成View树的布局过程。

    
      /**
    
        * <p>Derived classes should not override this method.
    
        * Derived classes with children should override
    
        * onLayout. In that method, they should
    
        * call layout on each of their children.</p>
    
        *
    
        * @param l Left position, relative to parent
    
        * @param t Top position, relative to parent
    
        * @param r Right position, relative to parent
    
        * @param b Bottom position, relative to parent
    
        */
    
        @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确定所有子元素的位置
    
                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);
    
                    }
    
                }
    
            }
    
            ......
    
            ......
    
        }
    
    

    把LinearLayout中onLayout的具体实现做分析:

    
    @Override
    
        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);
    
            }
    
        }
    
    // 具体的实现方法
    
    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);
    
                // 测量宽高
    
              final int childWidth = child.getMeasuredWidth();
    
              final int childHeight = child.getMeasuredHeight();
    
              ......
    
    // 为子元素指定对应的位置
    
    // 设置的就是测量的宽高
    
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child),
    
                            childWidth, childHeight);
    
                ......
    
                }
    
            }
    
        }
    
    private void setChildFrame(View child, int left, int top, int width, int height) {
    
            child.layout(left, top, left + width, top + height);
    
        }
    
    

    5.View的draw过程

    Draw过程相对比较简单,即将View绘制到屏幕上。从RootViewImpl中跟踪。观察源码:

    
    private void performDraw() {
    
            if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
    
                return;
    
            } else if (mView == null) {
    
                return;
    
            }
    
      ......
    
            boolean canUseAsync = draw(fullRedrawNeeded);
    
            ......
    
        }
    
        private boolean draw(boolean fullRedrawNeeded) {
    
    ......
    
              if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
    
                    scalingRequired, dirty, surfaceInsets)) {
    
                  return false;
    
                  }
    
    ......
    
            return useAsyncReport;
    
        }
    
        private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
    
                boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    
    ......
    
              mView.draw(canvas);
    
    ......
    
        }
    
    

    接着会调用到View的draw方法,其源码如下:

    
    @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);
    
    
    
                // Step 6, draw decorations (foreground, scrollbars)
    
                onDrawForeground(canvas);
    
                // Step 7, draw the default focus highlight
    
                drawDefaultFocusHighlight(canvas);
    
                if (debugDraw()) {
    
                    debugDrawFocus(canvas);
    
                }
    
                // we're done...
    
                return;
    
            }
    
    
    
            // Step 3, draw the content
    
            if (!dirtyOpaque) onDraw(canvas);
    
            // Step 4, draw the children
    
            dispatchDraw(canvas);
    
            // Step 5, draw the fade effect and restore layers
    
            final Paint p = scrollabilityCache.paint;
    
            final Matrix matrix = scrollabilityCache.matrix;
    
            final Shader fade = scrollabilityCache.shader;
    
    
    
            // Step 6, draw decorations (foreground, scrollbars)
    
            onDrawForeground(canvas);
    
    
    
        }
    
    

    注释已经解释的很清楚了,主要过程遵循如下几步:

    1. 绘制背景 drawBackground(canvas);

    2. 绘制内容 onDraw(canvas);

    3. 绘制children dispatchDraw(canvas);

    4. 绘制装饰 onDrawForeground(canvas);

    View绘制过程的传递是通过dispatchDraw()来实现的,它会遍历调用所有子元素的draw方法。

    View的工作原理就学习到这里了 !!! 太难了~!慢慢学习!!

    感谢:

    https://blog.csdn.net/xfhy_/article/details/90270630#comments

    参考:《Android 开发艺术探索》

    相关文章

      网友评论

          本文标题:Android View工作原理

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