View的繪制流程

1、View的繪制流程的開始
Android中有太多太多的方法可以開啟一個View的繪制流程,比如 view.setBackgroundColor() view.addView()等等。。

        LinearLayout linearLayout=new LinearLayout(this);
        linearLayout.setBackgroundColor(Color.parseColor("#ff0000"));// 可以開啟View的繪制流程
        linearLayout.addView(topView);// 可以開啟View的繪制流程

我們一步步來查看源碼,發現他們最后都調用到了View的requestLayout()方法,下面我們來看一下這個方法

    public void requestLayout() {
       // ...
       mParent.requestLayout();
       // ...
    }

我們發現,View的requestLayout() 最終調用了mParent.requestLayout();方法,這里的mParent其實就是 ViewRootImpl 這個類,為什么是這個類呢? 我們來從activity的啟動來分析一下

在ActivityThread類中

        private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // ...
        // 啟動activity 調用activity的onCreat()
        Activity a = performLaunchActivity(r, customIntent);

        // 調用activity的onResume()
        handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
    }

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        //  我們可以從源碼一步步跟蹤,發現這個 vm 就是WindowManagerImpl
        ViewManager wm = a.getWindowManager();
        // 我們這里就是調用WindowManagerImpl的addView()方法
        wm.addView(decor, l);
    }

然后我們來看WindowManagerImpl的addView()方法

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

繼續來看 mGlobal.addView()方法(WindowManagerGlobal 類中)

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // ...
        ViewRootImpl root;
        View panelParentView = null;
        // ...
        // 在這個方法創建ViewRootImpl類
        root = new ViewRootImpl(view.getContext(), display);
        // 調用ViewRootImpl類的setView()方法
        root.setView(view, wparams, panelParentView);
    }

繼續來到ViewRootImpl類的setView()方法

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
       // ...
       requestLayout();
       // ...
       // 我們來看最關鍵的這個方法,這里調用了View的assignParent()方法,并把ViewRootImpl類自己傳進去
       view.assignParent(this);
       // ...      
    }

我們繼續來看View的assignParent()方法

    void assignParent(ViewParent parent) {
        // 這里就是將前面我們要用的mParent 置為 ViewRootImpl
        if (mParent == null) {
            mParent = parent;
        } else if (parent == null) {
            mParent = null;
        } else {
            throw new RuntimeException("view " + this + " being added, but"
                    + " it already has a parent");
        }
    }

到此我們分析了View中 mParent.requestLayout(); 其實是調用了 ViewRootImpl 的 requestLayout() 方法

下面我們著重分析 ViewRootImpl 的 requestLayout() 方法

    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            // ...
            scheduleTraversals();
        }
    }

    void scheduleTraversals() {
        // ...
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        // ...
    }

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            // ...
            performTraversals();
            // ...
        }
    }

我們最終調用到performTraversals()這個方法,我們View的繪制流程從這里才剛剛開始

    private void performTraversals() {
        // ...
        // view的測量,用于指定和測量layout中所有控件的寬高
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        // ...
        // view的擺放
        performLayout(lp, mWidth, mHeight);
        // ...
        // view的繪制
        performDraw();
    }

1.1 我們先來看一下performMeasure方法

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        // ...
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        // ...
    }

    // 下面是View中的方法
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        // ...
        // 我們的第一個比較重要的方法出現了(測量view的寬高)
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        // ...        
    }

    // 這個時候我們就需要去看具體ViewGroup的onMeasure()方法,
    // 我們就用LinearLayout來做分析,其他的layout其實都一個套路,只是實現方式不一樣而已
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

下面我們就來看一下LinearLayout中的onMeasure()方法是如何實現的

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            // 豎方向的測量
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            // 橫方向的測量
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }


    // 我們就挑豎方向的測量來看一下
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        // ...
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            // 測量子view的寬與高
            // 這個時候,子view的寬與高才有了值
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight);
        }
        // 高度的size,有一套算法,每個item在豎方向疊加所得
        int heightSize = mTotalLength;
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        // 設置自己的寬與高
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);
    }

    void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        // 獲取子類的mode與size
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        // 獲取子類的mode與size
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
        // 調用子類measure方法,進一步調用子類的onMeasure()方法
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }


    // 結論: 子view的mode會根據父類的mode來共同決定
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        // 獲取父類的寬高模式
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // 父布局是一個指定的值 MeasureSpec.EXACTLY
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                // 子布局是一個指定的值,則resultSize = childDimension  并且子類的模式也是 MeasureSpec.EXACTLY;
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 子布局是一個MATCH_PARENT,則resultSize = size(這里的size為父類的size)  并且子類的模式也是 MeasureSpec.EXACTLY;
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 子布局是一個WRAP_CONTENT,則resultSize = size(這里的size為父類的size)  并且子類的模式也是 MeasureSpec.AT_MOST;
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 父布局是一個自適應布局 MeasureSpec.AT_MOST
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // 子布局是一個指定的值,則resultSize = childDimension  并且子類的模式也是 MeasureSpec.EXACTLY;
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 子布局是一個MATCH_PARENT,則resultSize = size(這里的size為父類的size)  并且子類的模式也是 MeasureSpec.AT_MOST;
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 子布局是一個WRAP_CONTENT,則resultSize = size(這里的size為父類的size)  并且子類的模式也是 MeasureSpec.AT_MOST;
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

1.2我們再來看一下performLayout方法

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
       // ...
       // 調用View的layout方法
       host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        // ...
    }

    // 然后來到View 的layout方法
    public void layout(int l, int t, int r, int b) {
        // ...
        // 調用自己的onLayout方法
        onLayout(changed, l, t, r, b);
        // ...
    }

    // 我們發現這里是一個空實現,這就要到具體的ViewGroup實現類中查看具體實現,我們也是拿LinearLayout來分析
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

    // 下面我們來看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);
        }
    }

    // 我們來看豎方向的layout方法
    void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;
        
        // ... 循環獲取子view,并擺放child View
        
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {// 如果子view不是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);
            }
        }
    }

    // 設置子view的layout
    private void setChildFrame(View child, int left, int top, int width, int height) {        
        child.layout(left, top, left + width, top + height);
    }

1.3我們再來看一下performDraw方法

    private void performDraw() {
        final boolean fullRedrawNeeded = mFullRedrawNeeded;
        mFullRedrawNeeded = false;

        draw(fullRedrawNeeded);
    }

    private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
             return;
        } 
    }

     private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        final Canvas canvas;
        mView.draw(canvas);
        return true;
     }

    // 然后我們調到View的draw方法
    public void draw(Canvas canvas) {
        // 畫背景
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // 有一個判斷,如果是ViewGroup的話,dirtyOpaque = true,所以ViewGroup是默認不會調用onDraw方法 通過onDraw()繪制自身內容;
        if (!dirtyOpaque) onDraw(canvas);

        // 空方法dispatchDraw()
        dispatchDraw(canvas);

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

    // 現在我們來看一下LinearLayout中的dispatchDraw方法
    // 通過dispatchDraw()繪制子View;
    protected void dispatchDraw(Canvas canvas) {
        // 循環獲取子view,并調用子view的onDraw方法
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    transientIndex = -1;
                }
            }
        }
    }

    // 調用此方法,就回調到子view的draw方法
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

總結一下:
1、總流程 : ViewRootImpl類中 requestLayout() -> scheduleTraversals() -> doTraversal() -> performTraversals() -> performMeasure() -> performLayout() -> performDraw()

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容