View的繪制流程

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

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

我們一步步來查看源碼,發(fā)現(xiàn)他們最后都調(diào)用到了View的requestLayout()方法仁讨,下面我們來看一下這個方法

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

我們發(fā)現(xiàn)随珠,View的requestLayout() 最終調(diào)用了mParent.requestLayout();方法灭袁,這里的mParent其實就是 ViewRootImpl 這個類,為什么是這個類呢窗看? 我們來從activity的啟動來分析一下

在ActivityThread類中

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

        // 調(diào)用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) {
        //  我們可以從源碼一步步跟蹤茸歧,發(fā)現(xiàn)這個 vm 就是WindowManagerImpl
        ViewManager wm = a.getWindowManager();
        // 我們這里就是調(diào)用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);
    }

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

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

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

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

我們繼續(xù)來看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(); 其實是調(diào)用了 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();
            // ...
        }
    }

我們最終調(diào)用到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) {
        // ...
        // 我們的第一個比較重要的方法出現(xiàn)了(測量view的寬高)
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        // ...        
    }

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

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

    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;
        // 設(shè)置自己的寬與高
        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);
        // 調(diào)用子類measure方法院尔,進(jìn)一步調(diào)用子類的onMeasure()方法
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }


    // 結(jié)論: 子view的mode會根據(jù)父類的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;

        // 父布局是一個自適應(yīng)布局 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) {
       // ...
       // 調(diào)用View的layout方法
       host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        // ...
    }

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

    // 我們發(fā)現(xiàn)這里是一個空實現(xiàn),這就要到具體的ViewGroup實現(xiàn)類中查看具體實現(xiàn)幻林,我們也是拿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;
        
        // ... 循環(huán)獲取子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);
            }
        }
    }

    // 設(shè)置子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;
     }

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

        // 有一個判斷音念,如果是ViewGroup的話,dirtyOpaque = true躏敢,所以ViewGroup是默認(rèn)不會調(diào)用onDraw方法 通過onDraw()繪制自身內(nèi)容;
        if (!dirtyOpaque) onDraw(canvas);

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

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

    // 現(xiàn)在我們來看一下LinearLayout中的dispatchDraw方法
    // 通過dispatchDraw()繪制子View;
    protected void dispatchDraw(Canvas canvas) {
        // 循環(huán)獲取子view闷愤,并調(diào)用子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;
                }
            }
        }
    }

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末讥脐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啼器,更是在濱河造成了極大的恐慌旬渠,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件端壳,死亡現(xiàn)場離奇詭異告丢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)损谦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門岖免,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人照捡,你說我怎么就攤上這事颅湘。” “怎么了麻敌?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵栅炒,是天一觀的道長。 經(jīng)常有香客問我术羔,道長赢赊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任级历,我火速辦了婚禮释移,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寥殖。我一直安慰自己玩讳,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布嚼贡。 她就那樣靜靜地躺著熏纯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粤策。 梳的紋絲不亂的頭發(fā)上樟澜,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼秩贰。 笑死霹俺,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毒费。 我是一名探鬼主播丙唧,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼觅玻!你這毒婦竟也來了想际?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤串塑,失蹤者是張志新(化名)和其女友劉穎沼琉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桩匪,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡打瘪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了傻昙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闺骚。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妆档,靈堂內(nèi)的尸體忽然破棺而出僻爽,到底是詐尸還是另有隱情,我是刑警寧澤贾惦,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布胸梆,位于F島的核電站,受9級特大地震影響须板,放射性物質(zhì)發(fā)生泄漏碰镜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一习瑰、第九天 我趴在偏房一處隱蔽的房頂上張望绪颖。 院中可真熱鬧,春花似錦甜奄、人聲如沸柠横。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牍氛。三九已至,卻和暖如春烟阐,著一層夾襖步出監(jiān)牢的瞬間糜俗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留悠抹,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓扩淀,卻偏偏與公主長得像楔敌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子驻谆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容