高級UI---LSN-2-2-UI繪制流程_UI具體繪制(布局流程)

前言

 前面我們已經(jīng)講了布局的測量喂很,現(xiàn)在我們來看一下寂屏,她是怎么完成布局的

2.View布局?jǐn)[放
在performTraversals的測量被調(diào)用之后轨域,我們繼續(xù)往后看會看到performLayout的調(diào)用祷舀,那么時從此處開始的我們的具體布局的擺放那么接下來我們需要了解她的具體布局時怎么操作的

 ...
  final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    boolean triggerGlobalLayoutListener = didLayout
            || mAttachInfo.mRecomputeGlobalAttributes;
    if (didLayout) {
        performLayout(lp, mWidth, mHeight);

        // By this point all views have been sized and positioned
        // We can compute the transparent area

        if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
            // start out transparent
            // TODO: AVOID THAT CALL BY CACHING THE RESULT?
            host.getLocationInWindow(mTmpLocation);
            mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
                    mTmpLocation[0] + host.mRight - host.mLeft,
                    mTmpLocation[1] + host.mBottom - host.mTop);

            host.gatherTransparentRegion(mTransparentRegion);
            if (mTranslator != null) {
                mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
            }

            if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
                mPreviousTransparentRegion.set(mTransparentRegion);
                mFullRedrawNeeded = true;
                // reconfigure window manager
                try {
                    mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
                } catch (RemoteException e) {
                }
            }
        }

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals -- after setFrame");
            host.debug();
        }
    }
 ...

接下來進(jìn)入performLayout()

 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    mLayoutRequested = false;
    mScrollMayChange = true;
    mInLayout = true;

    final View host = mView;
    if (host == null) {
        return;
    }
    if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
        Log.v(mTag, "Laying out " + host + " to (" +
                host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
    }

    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

        mInLayout = false;
        int numViewsRequestingLayout = mLayoutRequesters.size();
        if (numViewsRequestingLayout > 0) {
            // requestLayout() was called during layout.
            // If no layout-request flags are set on the requesting views, there is no problem.
            // If some requests are still pending, then we need to clear those flags and do
            // a full request/measure/layout pass to handle this situation.
            ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                    false);
            if (validLayoutRequesters != null) {
                // Set this flag to indicate that any further requests are happening during
                // the second pass, which may result in posting those requests to the next
                // frame instead
                mHandlingLayoutInLayoutRequest = true;

                // Process fresh layout requests, then measure and layout
                int numValidRequests = validLayoutRequesters.size();
                for (int i = 0; i < numValidRequests; ++i) {
                    final View view = validLayoutRequesters.get(i);
                    Log.w("View", "requestLayout() improperly called by " + view +
                            " during layout: running second layout pass");
                    view.requestLayout();
                }
                measureHierarchy(host, lp, mView.getContext().getResources(),
                        desiredWindowWidth, desiredWindowHeight);
                mInLayout = true;
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                mHandlingLayoutInLayoutRequest = false;

                // Check the valid requests again, this time without checking/clearing the
                // layout flags, since requests happening during the second pass get noop'd
                validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                if (validLayoutRequesters != null) {
                    final ArrayList<View> finalRequesters = validLayoutRequesters;
                    // Post second-pass requests to the next frame
                    getRunQueue().post(new Runnable() {
                        @Override
                        public void run() {
                            int numValidRequests = finalRequesters.size();
                            for (int i = 0; i < numValidRequests; ++i) {
                                final View view = finalRequesters.get(i);
                                Log.w("View", "requestLayout() improperly called by " + view +
                                        " during second layout pass: posting in next frame");
                                view.requestLayout();
                            }
                        }
                    });
                }
            }

        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}

在這里我們可以觀察到host就是DectorView鉴未,而在下面舶吗,這里調(diào)用了host.layout
在這里會吧起始點x=0,y=0傳入,然后講測量好的寬高傳入

  public void layout(int l, int t, int r, int b) {
    //如果不是第一次歹颓,跳過否則會在此進(jìn)行測量坯屿,意思是第一次進(jìn)來會進(jìn)行一次測量用于保存寬高,意義在于優(yōu)化巍扛,接著往下看
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }

  // 初次進(jìn)行上下左右點的初始化
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
   //這里調(diào)用了setFrame進(jìn)行初始化mLeft,mRight,mTop,mBottom這四個值
    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);
            }
        }
    }

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

    if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
        mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
        notifyEnterOrExitForAutoFillIfNeeded(true);
    }
}

在上面我門會看到對于我layout最主要做的一件事情就是對我們的上下左右四個點進(jìn)行初始化领跛,在這里時通過setFrame()

 protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if (DBG) {
        Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                + right + "," + bottom + ")");
    }

    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;

        // Remember our drawn bit
        int drawn = mPrivateFlags & PFLAG_DRAWN;

        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

        // Invalidate our old position(使我們舊的信息無效化)
        invalidate(sizeChanged);
        //重新初始化定位
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

        mPrivateFlags |= PFLAG_HAS_BOUNDS;


        if (sizeChanged) {
            sizeChange(newWidth, newHeight, oldWidth, oldHeight);
        }

        if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
            // If we are visible, force the DRAWN bit to on so that
            // this invalidate will go through (at least to our parent).
            // This is because someone may have invalidated this view
            // before this call to setFrame came in, thereby clearing
            // the DRAWN bit.
            mPrivateFlags |= PFLAG_DRAWN;
            invalidate(sizeChanged);
            // parent display list may need to be recreated based on a change in the bounds
            // of any child
            invalidateParentCaches();
        }

        // Reset drawn bit to original value (invalidate turns it off)
        mPrivateFlags |= drawn;

        mBackgroundSizeChanged = true;
        mDefaultFocusHighlightSizeChanged = true;
        if (mForegroundInfo != null) {
            mForegroundInfo.mBoundsChanged = true;
        }

        notifySubtreeAccessibilityStateChangedIfNeeded();
    }
    return changed;
}

setFrame在進(jìn)行初始化的時候會對比上一次是否一致,若一致則不會在此進(jìn)行撤奸,若是一致吠昭,則會使我們舊的信息直接失效invalidate(sizeChanged);
那么這里布局?jǐn)[放就差不多完成了,但是貌似我門沒有看到DectorView的子View進(jìn)行布局胧瓜,這個時候我們想起之前好想是調(diào)用了當(dāng)前DectorView的onLayout矢棚,也就是View當(dāng)中的onLayout

  /**
 * Called from layout when this view should
 * assign a size and position to each of its children.
 *
 * Derived classes with children should override
 * this method and call layout on each of
 * their children.
 * @param changed This is a new size or position for this view
 * @param left Left position, relative to parent
 * @param top Top position, relative to parent
 * @param right Right position, relative to parent
 * @param bottom Bottom position, relative to parent
 */
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

這是我看到了一段空的代碼,完全不明所以府喳, 但是這個時候我想起來蒲肋,當(dāng)前在這個類當(dāng)中host的原型是View, 而我們真正在使用的時候是一個DectorView
這時我門找到了

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

這時發(fā)現(xiàn)他調(diào)用了爸爸的onLayout而她的爸爸是FreamLayout所以钝满,找到最終目標(biāo)

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

這里請注意兜粘,有個核心問題要注意的是同之前我門所講的測量流程, 我的布局也是同樣弯蚜, 每一個不同布局組件她們的實現(xiàn)是不一樣的
而在這里我們以FreamLayout舉例孔轴,
在這里他開始調(diào)用了一個
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);
        }
    }
}

這個時候我們會發(fā)現(xiàn),當(dāng)前我們的組件在不斷的迭代當(dāng)前的子view碎捺,然后讓她們開始調(diào)用自己layout方法進(jìn)行定位路鹰,所以直接從此處可以看出來,當(dāng)前我們的布局?jǐn)[放流程實際上是牵寺,先得到頂層悍引, 頂層自己先開始layout進(jìn)行布局定位恩脂,然后調(diào)用onLayout調(diào)用子view讓子view調(diào)用自己的layout對自己進(jìn)行定位以達(dá)到定位的所有目的帽氓,

總結(jié):
那么其實我門只要清楚了當(dāng)前的繪制流程和布局流程,我門需要開發(fā)自己自定義的布局其實實際上就只需要添加我門自己的業(yè)務(wù)代碼俩块,不管是FreamLayout,還是LinearLayout等官方提供出來的布局組件黎休, 都是依照這套機(jī)制來玩的, 只不過是添加了她們的業(yè)務(wù)玉凯,實現(xiàn)了相對應(yīng)的效果势腮。

所以,至此漫仆,繪制流程ok!

著作:Kerwin Barry
郵箱:kerwin0210@sina.com
原創(chuàng)博客捎拯,轉(zhuǎn)載請注明出處.....

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市盲厌,隨后出現(xiàn)的幾起案子署照,更是在濱河造成了極大的恐慌祸泪,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件建芙,死亡現(xiàn)場離奇詭異没隘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)禁荸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門右蒲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赶熟,你說我怎么就攤上這事瑰妄。” “怎么了钧大?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵翰撑,是天一觀的道長。 經(jīng)常有香客問我啊央,道長眶诈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任瓜饥,我火速辦了婚禮逝撬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乓土。我一直安慰自己宪潮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布趣苏。 她就那樣靜靜地躺著狡相,像睡著了一般。 火紅的嫁衣襯著肌膚如雪食磕。 梳的紋絲不亂的頭發(fā)上尽棕,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音彬伦,去河邊找鬼滔悉。 笑死,一個胖子當(dāng)著我的面吹牛单绑,可吹牛的內(nèi)容都是我干的回官。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼搂橙,長吁一口氣:“原來是場噩夢啊……” “哼歉提!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤苔巨,失蹤者是張志新(化名)和其女友劉穎弯屈,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恋拷,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡资厉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蔬顾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宴偿。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诀豁,靈堂內(nèi)的尸體忽然破棺而出窄刘,到底是詐尸還是另有隱情,我是刑警寧澤舷胜,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布娩践,位于F島的核電站,受9級特大地震影響烹骨,放射性物質(zhì)發(fā)生泄漏翻伺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一沮焕、第九天 我趴在偏房一處隱蔽的房頂上張望吨岭。 院中可真熱鬧,春花似錦峦树、人聲如沸辣辫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽急灭。三九已至,卻和暖如春谷遂,著一層夾襖步出監(jiān)牢的瞬間葬馋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工埋凯, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留点楼,地道東北人扫尖。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓白对,卻偏偏與公主長得像,于是被迫代替她去往敵國和親换怖。 傳聞我的和親對象是個殘疾皇子甩恼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

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