Android RelativeLayout源碼學(xué)習(xí)

Overview

RelativeLayout是Android中一種常用的布局剔交,通過View之間的相互關(guān)系能夠方便地確定子View的位置,通過源碼的學(xué)習(xí)來看下Android是如何實(shí)現(xiàn)這一功能的。Android中的一個(gè)組件灿渴,工作原理主要在onMeasure(),onLayout(),onDraw()這3大階段中叹侄。

onMeasure()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mDirtyHierarchy) {
            mDirtyHierarchy = false;
            sortChildren();
        }
        // ……
}

測(cè)量過程的第一步是對(duì)子View進(jìn)行排序,在這一步驟會(huì)設(shè)置一個(gè)標(biāo)志位mDirtyHierarchy進(jìn)行優(yōu)化装诡,下面看下sortChildren的具體實(shí)現(xiàn):

private void sortChildren() {
        final int count = getChildCount();
        if (mSortedVerticalChildren == null || mSortedVerticalChildren.length != count) {
            mSortedVerticalChildren = new View[count];
        }

        if (mSortedHorizontalChildren == null || mSortedHorizontalChildren.length != count) {
            mSortedHorizontalChildren = new View[count];
        }

        final DependencyGraph graph = mGraph;
        graph.clear();

        for (int i = 0; i < count; i++) {
            graph.add(getChildAt(i));
        }

        graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL);
        graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL);
    }

這個(gè)函數(shù)包含以下步驟:

  • 初始化兩個(gè)數(shù)組银受,分別用于記錄水平方向和豎直方向的排序結(jié)果
  • 初始化一個(gè)依賴圖DependencyGraph,將子View都加入到依賴圖中
  • 調(diào)用DependencyGraph的getSortedViews方法鸦采,將兩個(gè)方向的排序結(jié)果記錄到對(duì)應(yīng)數(shù)組中

下面首先看下DependencyGraph的結(jié)構(gòu):

private static class DependencyGraph {
        /**
         * List of all views in the graph.
         */
        private ArrayList<Node> mNodes = new ArrayList<Node>();

        /**
         * List of nodes in the graph. Each node is identified by its
         * view id (see View#getId()).
         */
        private SparseArray<Node> mKeyNodes = new SparseArray<Node>();

        /**
         * Temporary data structure used to build the list of roots
         * for this graph.
         */
        private ArrayDeque<Node> mRoots = new ArrayDeque<Node>();
}

結(jié)果里每個(gè)成員代表的含義宾巍,其注釋已經(jīng)說清楚了;再具體看下Node的結(jié)構(gòu):

static class Node {
            /**
             * The view representing this node in the layout.
             */
            View view;

            /**
             * The list of dependents for this node; a dependent is a node
             * that needs this node to be processed first.
             */
            final ArrayMap<Node, DependencyGraph> dependents =
                    new ArrayMap<Node, DependencyGraph>();

            /**
             * The list of dependencies for this node.
             */
            final SparseArray<Node> dependencies = new SparseArray<Node>();
}

根據(jù)注釋也能夠明白每個(gè)成員的含義

接下來看下DependencyGraph是如何實(shí)現(xiàn)getSortedViews方法的:

void getSortedViews(View[] sorted, int... rules) {
            // 找出所有的不依賴其他節(jié)點(diǎn)的節(jié)點(diǎn)渔伯,即根節(jié)點(diǎn)
            final ArrayDeque<Node> roots = findRoots(rules);
            int index = 0;

            Node node;
            // 循環(huán)從根節(jié)點(diǎn)的隊(duì)尾中取出元素
            while ((node = roots.pollLast()) != null) {
                final View view = node.view;
                final int key = view.getId();
                // 將該節(jié)點(diǎn)加入排序結(jié)果
                sorted[index++] = view;
                // 取出該節(jié)點(diǎn)的所有伴隨節(jié)點(diǎn)
                final ArrayMap<Node, DependencyGraph> dependents = node.dependents;
                final int count = dependents.size();
                for (int i = 0; i < count; i++) {
                    // 遍歷伴隨節(jié)點(diǎn)
                    final Node dependent = dependents.keyAt(i);
                    // 取出伴隨節(jié)點(diǎn)的所有依賴節(jié)點(diǎn)集合
                    final SparseArray<Node> dependencies = dependent.dependencies;
                    // 將當(dāng)前節(jié)點(diǎn)從伴隨節(jié)點(diǎn)的依賴節(jié)點(diǎn)集合中去掉
                    dependencies.remove(key);
                    // 如果該伴隨節(jié)點(diǎn)去掉當(dāng)前節(jié)點(diǎn)的依賴之后顶霞,沒有再依賴其他節(jié)點(diǎn),證明它是新的根節(jié)點(diǎn),加入根節(jié)點(diǎn)集合
                    if (dependencies.size() == 0) {
                        roots.add(dependent);
                    }
                }
            }

            if (index < sorted.length) {
                throw new IllegalStateException("Circular dependencies cannot exist"
                        + " in RelativeLayout");
            }
        }

這個(gè)排序的過程已經(jīng)在注釋中說明选浑,實(shí)現(xiàn)過程還是挺明確的蓝厌。

再看完排序過程,我們回到onMeasure()方法中

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // …… 
        int myWidth = -1;
        int myHeight = -1;

        int width = 0;
        int height = 0;

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // Record our dimensions if they are known;
        if (widthMode != MeasureSpec.UNSPECIFIED) {
            myWidth = widthSize;
        }

        if (heightMode != MeasureSpec.UNSPECIFIED) {
            myHeight = heightSize;
        }

        if (widthMode == MeasureSpec.EXACTLY) {
            width = myWidth;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = myHeight;
        }

        View ignore = null;
        int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
        final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;
        gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;

        int left = Integer.MAX_VALUE;
        int top = Integer.MAX_VALUE;
        int right = Integer.MIN_VALUE;
        int bottom = Integer.MIN_VALUE;

        boolean offsetHorizontalAxis = false;
        boolean offsetVerticalAxis = false;

        if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
            ignore = findViewById(mIgnoreGravity);
        }

        final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
        final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;

        // We need to know our size for doing the correct computation of children positioning in RTL
        // mode but there is no practical way to get it instead of running the code below.
        // So, instead of running the code twice, we just set the width to a "default display width"
        // before the computation and then, as a last pass, we will update their real position with
        // an offset equals to "DEFAULT_WIDTH - width".
        final int layoutDirection = getLayoutDirection();
        if (isLayoutRtl() && myWidth == -1) {
            myWidth = DEFAULT_WIDTH;
        }
        // ……
}

中間這段是一些輔助變量的初始化古徒,就不多說了拓提,繼續(xù)往下看

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // …… 
        View[] views = mSortedHorizontalChildren;
        int count = views.length;

        for (int i = 0; i < count; i++) {
            View child = views[i];
            if (child.getVisibility() != GONE) {
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                int[] rules = params.getRules(layoutDirection);

                applyHorizontalSizeRules(params, myWidth, rules);
                measureChildHorizontal(child, params, myWidth, myHeight);

                if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
                    offsetHorizontalAxis = true;
                }
            }
        }

        // ……
}

測(cè)量過程的第二步是遍歷子View進(jìn)行水平方向的依賴測(cè)量,這一步通過3個(gè)函數(shù)進(jìn)行隧膘,逐個(gè)看下函數(shù)的實(shí)現(xiàn)代态。

介紹函數(shù)實(shí)現(xiàn)之前先介紹一下rule的概念,在RelativeLayout.LayoutParams進(jìn)行解析時(shí)疹吃,會(huì)將RelativeLayout中的子View的依賴關(guān)系通過rules數(shù)組存儲(chǔ)起來蹦疑,數(shù)組中的下標(biāo)代表某條規(guī)則,例如LEFT_OF萨驶,對(duì)應(yīng)的元素代表這條規(guī)則依賴的View id:

switch (attr) {
     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
           rules[LEFT_OF] = a.getResourceId(attr, 0);
     break;
                    
}

了解了rule的概念之后歉摧,看下遍歷子View進(jìn)行依賴測(cè)量時(shí)相關(guān)方法的實(shí)現(xiàn):

private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
        RelativeLayout.LayoutParams anchorParams;

        // VALUE_NOT_SET indicates a "soft requirement" in that direction. For example:
        // left=10, right=VALUE_NOT_SET means the view must start at 10, but can go as far as it
        // wants to the right
        // left=VALUE_NOT_SET, right=10 means the view must end at 10, but can go as far as it
        // wants to the left
        // left=10, right=20 means the left and right ends are both fixed
        childParams.mLeft = VALUE_NOT_SET;
        childParams.mRight = VALUE_NOT_SET;

        anchorParams = getRelatedViewParams(rules, LEFT_OF);
        if (anchorParams != null) {
            childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
                    childParams.rightMargin);
        } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) {
            if (myWidth >= 0) {
                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
            }
        }

        anchorParams = getRelatedViewParams(rules, RIGHT_OF);
        if (anchorParams != null) {
            childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin +
                    childParams.leftMargin);
        } else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) {
            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
        }

        anchorParams = getRelatedViewParams(rules, ALIGN_LEFT);
        if (anchorParams != null) {
            childParams.mLeft = anchorParams.mLeft + childParams.leftMargin;
        } else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) {
            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
        }

        anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT);
        if (anchorParams != null) {
            childParams.mRight = anchorParams.mRight - childParams.rightMargin;
        } else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) {
            if (myWidth >= 0) {
                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
            }
        }

        if (0 != rules[ALIGN_PARENT_LEFT]) {
            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
        }

        if (0 != rules[ALIGN_PARENT_RIGHT]) {
            if (myWidth >= 0) {
                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
            }
        }
    }

縱觀整個(gè)函數(shù),其實(shí)就是對(duì)水平方向的幾條規(guī)則逐個(gè)進(jìn)行處理腔呜,這幾條規(guī)則分別是:LEFT_OF/RIGHT_OF/ALIGN_LEFT/ALIGN_RIGHT/ALIGN_PARENT_LEFT/ALIGN_PARENT_RIGHT叁温;
對(duì)于每個(gè)屬性的處理方法都是相似的:通過相應(yīng)的規(guī)則從getRelativeViewParams中取到對(duì)應(yīng)依賴的View,然后通過依賴的View的對(duì)應(yīng)屬性和padding來調(diào)整該View的對(duì)應(yīng)屬性
看看下getRelativeViewParams的實(shí)現(xiàn):

private LayoutParams getRelatedViewParams(int[] rules, int relation) {
        View v = getRelatedView(rules, relation);
        if (v != null) {
            ViewGroup.LayoutParams params = v.getLayoutParams();
            if (params instanceof LayoutParams) {
                return (LayoutParams) v.getLayoutParams();
            }
        }
        return null;
    }

真正的實(shí)現(xiàn)在getRelatedView中

private View getRelatedView(int[] rules, int relation) {
        int id = rules[relation];
        if (id != 0) {
            DependencyGraph.Node node = mGraph.mKeyNodes.get(id);
            if (node == null) return null;
            View v = node.view;

            // Find the first non-GONE view up the chain
            while (v.getVisibility() == View.GONE) {
                rules = ((LayoutParams) v.getLayoutParams()).getRules(v.getLayoutDirection());
                node = mGraph.mKeyNodes.get((rules[relation]));
                // ignore self dependency. for more info look in git commit: da3003
                if (node == null || v == node.view) return null;
                v = node.view;
            }

            return v;
        }

        return null;
    }

關(guān)鍵點(diǎn):

  • 通過rules數(shù)組取到對(duì)應(yīng)依賴的View id
  • 通過View id從依賴圖中取出對(duì)應(yīng)的View
  • 如果取到的View可見性為GONE育谬,則繼續(xù)通過對(duì)應(yīng)的依賴規(guī)則取依賴的View所依賴的View(有點(diǎn)拗口...)

經(jīng)過applyHorizontalSizeRules方法的處理之后券盅,子View的params中的left/right值都已經(jīng)確定好,然后會(huì)進(jìn)入measureChildHorizontal方法

private void measureChildHorizontal(
            View child, LayoutParams params, int myWidth, int myHeight) {
        final int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight,
                params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight,
                myWidth);

        final int childHeightMeasureSpec;
        if (myHeight < 0 && !mAllowBrokenMeasureSpecs) {
            if (params.height >= 0) {
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        params.height, MeasureSpec.EXACTLY);
            } else {
                // Negative values in a mySize/myWidth/myWidth value in
                // RelativeLayout measurement is code for, "we got an
                // unspecified mode in the RelativeLayout's measure spec."
                // Carry it forward.
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            }
        } else {
            final int maxHeight;
            if (mMeasureVerticalWithPaddingMargin) {
                maxHeight = Math.max(0, myHeight - mPaddingTop - mPaddingBottom
                        - params.topMargin - params.bottomMargin);
            } else {
                maxHeight = Math.max(0, myHeight);
            }

            final int heightMode;
            if (params.height == LayoutParams.MATCH_PARENT) {
                heightMode = MeasureSpec.EXACTLY;
            } else {
                heightMode = MeasureSpec.AT_MOST;
            }
            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, heightMode);
        }

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

在上一階段我們確定了子View的left/right參數(shù)膛檀,在這一階段锰镀,可以通過left/right得到子View的寬度,然后進(jìn)行第一次子View的measure方法調(diào)用咖刃,進(jìn)行第一次測(cè)量

接下來調(diào)用positionChildHorizontal方法:

private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth,
            boolean wrapContent) {

        final int layoutDirection = getLayoutDirection();
        int[] rules = params.getRules(layoutDirection);

        if (params.mLeft == VALUE_NOT_SET && params.mRight != VALUE_NOT_SET) {
            // Right is fixed, but left varies
            params.mLeft = params.mRight - child.getMeasuredWidth();
        } else if (params.mLeft != VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
            // Left is fixed, but right varies
            params.mRight = params.mLeft + child.getMeasuredWidth();
        } else if (params.mLeft == VALUE_NOT_SET && params.mRight == VALUE_NOT_SET) {
            // Both left and right vary
            if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
                if (!wrapContent) {
                    centerHorizontal(child, params, myWidth);
                } else {
                    positionAtEdge(child, params, myWidth);
                }
                return true;
            } else {
                // This is the default case. For RTL we start from the right and for LTR we start
                // from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL.
                positionAtEdge(child, params, myWidth);
            }
        }
        return rules[ALIGN_PARENT_END] != 0;
    }

這個(gè)方法的代碼不長(zhǎng)泳炉,從實(shí)現(xiàn)中可以得知,該方法的作用在于嚎杨,那些在前階段沒有設(shè)置的左右邊界約束花鹅。

接下來是第三步,遍歷子View進(jìn)行垂直方向的依賴測(cè)量

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// ……
    for (int i = 0; i < count; i++) {
            final View child = views[i];
            if (child.getVisibility() != GONE) {
                final LayoutParams params = (LayoutParams) child.getLayoutParams();

                applyVerticalSizeRules(params, myHeight, child.getBaseline());
                measureChild(child, params, myWidth, myHeight);
                if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
                    offsetVerticalAxis = true;
                }

                if (isWrapContentWidth) {
                    if (isLayoutRtl()) {
                        if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                            width = Math.max(width, myWidth - params.mLeft);
                        } else {
                            width = Math.max(width, myWidth - params.mLeft + params.leftMargin);
                        }
                    } else {
                        if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                            width = Math.max(width, params.mRight);
                        } else {
                            width = Math.max(width, params.mRight + params.rightMargin);
                        }
                    }
                }

                if (isWrapContentHeight) {
                    if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                        height = Math.max(height, params.mBottom);
                    } else {
                        height = Math.max(height, params.mBottom + params.bottomMargin);
                    }
                }

                if (child != ignore || verticalGravity) {
                    left = Math.min(left, params.mLeft - params.leftMargin);
                    top = Math.min(top, params.mTop - params.topMargin);
                }

                if (child != ignore || horizontalGravity) {
                    right = Math.max(right, params.mRight + params.rightMargin);
                    bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
                }
            }
        }
// ……
}

垂直方向的約束測(cè)量過程和水平方向是一個(gè)套路:

  • 遍歷子View枫浙,根據(jù)垂直方向的規(guī)則進(jìn)行top/bottom的約束
  • 測(cè)量一次子View
  • 補(bǔ)充邊界約束
  • 不同的是刨肃,在垂直方向的測(cè)量階段,還會(huì)對(duì)RelativeLayout的最大寬度和最大高度進(jìn)行計(jì)算

經(jīng)過水平和垂直方向兩輪對(duì)子View的分發(fā)測(cè)量箩帚,已經(jīng)完成了子View的測(cè)量過程真友,接下來是根據(jù)是否wrapContent調(diào)整RelativeLayout自身的高度

    if (isWrapContentWidth) {
            // Width already has left padding in it since it was calculated by looking at
            // the right of each child view
            width += mPaddingRight;

            if (mLayoutParams != null && mLayoutParams.width >= 0) {
                width = Math.max(width, mLayoutParams.width);
            }

            width = Math.max(width, getSuggestedMinimumWidth());
            width = resolveSize(width, widthMeasureSpec);

            if (offsetHorizontalAxis) {
                for (int i = 0; i < count; i++) {
                    final View child = views[i];
                    if (child.getVisibility() != GONE) {
                        final LayoutParams params = (LayoutParams) child.getLayoutParams();
                        final int[] rules = params.getRules(layoutDirection);
                        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
                            centerHorizontal(child, params, width);
                        } else if (rules[ALIGN_PARENT_RIGHT] != 0) {
                            final int childWidth = child.getMeasuredWidth();
                            params.mLeft = width - mPaddingRight - childWidth;
                            params.mRight = params.mLeft + childWidth;
                        }
                    }
                }
            }
        }

        if (isWrapContentHeight) {
            // Height already has top padding in it since it was calculated by looking at
            // the bottom of each child view
            height += mPaddingBottom;

            if (mLayoutParams != null && mLayoutParams.height >= 0) {
                height = Math.max(height, mLayoutParams.height);
            }

            height = Math.max(height, getSuggestedMinimumHeight());
            height = resolveSize(height, heightMeasureSpec);

            if (offsetVerticalAxis) {
                for (int i = 0; i < count; i++) {
                    final View child = views[i];
                    if (child.getVisibility() != GONE) {
                        final LayoutParams params = (LayoutParams) child.getLayoutParams();
                        final int[] rules = params.getRules(layoutDirection);
                        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
                            centerVertical(child, params, height);
                        } else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
                            final int childHeight = child.getMeasuredHeight();
                            params.mTop = height - mPaddingBottom - childHeight;
                            params.mBottom = params.mTop + childHeight;
                        }
                    }
                }
            }
        }

然后再根據(jù)Gravity屬性調(diào)整位置

    if (isWrapContentWidth) {
            // Width already has left padding in it since it was calculated by looking at
            // the right of each child view
            width += mPaddingRight;

            if (mLayoutParams != null && mLayoutParams.width >= 0) {
                width = Math.max(width, mLayoutParams.width);
            }

            width = Math.max(width, getSuggestedMinimumWidth());
            width = resolveSize(width, widthMeasureSpec);

            if (offsetHorizontalAxis) {
                for (int i = 0; i < count; i++) {
                    final View child = views[i];
                    if (child.getVisibility() != GONE) {
                        final LayoutParams params = (LayoutParams) child.getLayoutParams();
                        final int[] rules = params.getRules(layoutDirection);
                        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
                            centerHorizontal(child, params, width);
                        } else if (rules[ALIGN_PARENT_RIGHT] != 0) {
                            final int childWidth = child.getMeasuredWidth();
                            params.mLeft = width - mPaddingRight - childWidth;
                            params.mRight = params.mLeft + childWidth;
                        }
                    }
                }
            }
        }

        if (isWrapContentHeight) {
            // Height already has top padding in it since it was calculated by looking at
            // the bottom of each child view
            height += mPaddingBottom;

            if (mLayoutParams != null && mLayoutParams.height >= 0) {
                height = Math.max(height, mLayoutParams.height);
            }

            height = Math.max(height, getSuggestedMinimumHeight());
            height = resolveSize(height, heightMeasureSpec);

            if (offsetVerticalAxis) {
                for (int i = 0; i < count; i++) {
                    final View child = views[i];
                    if (child.getVisibility() != GONE) {
                        final LayoutParams params = (LayoutParams) child.getLayoutParams();
                        final int[] rules = params.getRules(layoutDirection);
                        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
                            centerVertical(child, params, height);
                        } else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
                            final int childHeight = child.getMeasuredHeight();
                            params.mTop = height - mPaddingBottom - childHeight;
                            params.mBottom = params.mTop + childHeight;
                        }
                    }
                }
            }
        }

最后就是設(shè)置自身的寬高,完成測(cè)量過程

      if (isLayoutRtl()) {
            final int offsetWidth = myWidth - width;
            for (int i = 0; i < count; i++) {
                final View child = views[i];
                if (child.getVisibility() != GONE) {
                    final LayoutParams params = (LayoutParams) child.getLayoutParams();
                    params.mLeft -= offsetWidth;
                    params.mRight -= offsetWidth;
                }
            }
        }

        setMeasuredDimension(width, height);

上述就是onMeasure的過程紧帕。

onLayout

protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //  The layout has actually already been performed and the positions
        //  cached.  Apply the cached values to the children.
        final int count = getChildCount();

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                RelativeLayout.LayoutParams st =
                        (RelativeLayout.LayoutParams) child.getLayoutParams();
                child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
            }
        }
    }

onLayout的過程很簡(jiǎn)單盔然,遍歷子View,因?yàn)樽覸iew在分發(fā)測(cè)量過程中已經(jīng)確定了上下左右的邊界約束,所以直接調(diào)用對(duì)應(yīng)的約束值進(jìn)行l(wèi)ayout愈案。

onDraw

在RelativeLayout中挺尾,沒有對(duì)onDraw進(jìn)行重寫。

總結(jié)

和大多數(shù)容器布局類似站绪,RelativeLayout的核心在于測(cè)量階段遭铺,需要遍歷兩次子View,原因是水平和垂直方向各需一次來確定相應(yīng)方向的邊界約束恢准。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末掂僵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子顷歌,更是在濱河造成了極大的恐慌,老刑警劉巖幔睬,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眯漩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡麻顶,警方通過查閱死者的電腦和手機(jī)赦抖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辅肾,“玉大人队萤,你說我怎么就攤上這事〗玫觯” “怎么了要尔?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)新娜。 經(jīng)常有香客問我赵辕,道長(zhǎng),這世上最難降的妖魔是什么概龄? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任还惠,我火速辦了婚禮,結(jié)果婚禮上私杜,老公的妹妹穿的比我還像新娘蚕键。我一直安慰自己,他們只是感情好衰粹,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布锣光。 她就那樣靜靜地躺著,像睡著了一般寄猩。 火紅的嫁衣襯著肌膚如雪嫉晶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天箍铭,我揣著相機(jī)與錄音,去河邊找鬼椎镣。 笑死,一個(gè)胖子當(dāng)著我的面吹牛状答,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惊科,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼馆截!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜡娶,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窖张,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宿接,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年拥知,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碎赢。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖襟齿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情枕赵,我是刑警寧澤猜欺,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站拷窜,受9級(jí)特大地震影響开皿,放射性物質(zhì)發(fā)生泄漏涧黄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一赋荆、第九天 我趴在偏房一處隱蔽的房頂上張望笋妥。 院中可真熱鬧,春花似錦窄潭、人聲如沸春宣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽月帝。三九已至,卻和暖如春幽污,著一層夾襖步出監(jiān)牢的瞬間嚷辅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工距误, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留潦蝇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓深寥,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親贤牛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子惋鹅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351