Java tutorial
/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import android.annotation.NonNull; import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.os.Build; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Pools.SynchronizedPool; import android.util.SparseArray; import android.view.Gravity; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewHierarchyEncoder; import android.view.accessibility.AccessibilityEvent; import android.widget.RemoteViews.RemoteView; import com.android.internal.R; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Comparator; import java.util.SortedSet; import java.util.TreeSet; /** * A Layout where the positions of the children can be described in relation to each other or to the * parent. * * <p> * Note that you cannot have a circular dependency between the size of the RelativeLayout and the * position of its children. For example, you cannot have a RelativeLayout whose height is set to * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT WRAP_CONTENT} and a child set to * {@link #ALIGN_PARENT_BOTTOM}. * </p> * * <p><strong>Note:</strong> In platform version 17 and lower, RelativeLayout was affected by * a measurement bug that could cause child views to be measured with incorrect * {@link android.view.View.MeasureSpec MeasureSpec} values. (See * {@link android.view.View.MeasureSpec#makeMeasureSpec(int, int) MeasureSpec.makeMeasureSpec} * for more details.) This was triggered when a RelativeLayout container was placed in * a scrolling container, such as a ScrollView or HorizontalScrollView. If a custom view * not equipped to properly measure with the MeasureSpec mode * {@link android.view.View.MeasureSpec#UNSPECIFIED UNSPECIFIED} was placed in a RelativeLayout, * this would silently work anyway as RelativeLayout would pass a very large * {@link android.view.View.MeasureSpec#AT_MOST AT_MOST} MeasureSpec instead.</p> * * <p>This behavior has been preserved for apps that set <code>android:targetSdkVersion="17"</code> * or older in their manifest's <code>uses-sdk</code> tag for compatibility. Apps targeting SDK * version 18 or newer will receive the correct behavior</p> * * <p>See the <a href="{@docRoot}guide/topics/ui/layout/relative.html">Relative * Layout</a> guide.</p> * * <p> * Also see {@link android.widget.RelativeLayout.LayoutParams RelativeLayout.LayoutParams} for * layout attributes * </p> * * @attr ref android.R.styleable#RelativeLayout_gravity * @attr ref android.R.styleable#RelativeLayout_ignoreGravity */ @RemoteView public class RelativeLayout extends ViewGroup { public static final int TRUE = -1; /** * Rule that aligns a child's right edge with another child's left edge. */ public static final int LEFT_OF = 0; /** * Rule that aligns a child's left edge with another child's right edge. */ public static final int RIGHT_OF = 1; /** * Rule that aligns a child's bottom edge with another child's top edge. */ public static final int ABOVE = 2; /** * Rule that aligns a child's top edge with another child's bottom edge. */ public static final int BELOW = 3; /** * Rule that aligns a child's baseline with another child's baseline. */ public static final int ALIGN_BASELINE = 4; /** * Rule that aligns a child's left edge with another child's left edge. */ public static final int ALIGN_LEFT = 5; /** * Rule that aligns a child's top edge with another child's top edge. */ public static final int ALIGN_TOP = 6; /** * Rule that aligns a child's right edge with another child's right edge. */ public static final int ALIGN_RIGHT = 7; /** * Rule that aligns a child's bottom edge with another child's bottom edge. */ public static final int ALIGN_BOTTOM = 8; /** * Rule that aligns the child's left edge with its RelativeLayout * parent's left edge. */ public static final int ALIGN_PARENT_LEFT = 9; /** * Rule that aligns the child's top edge with its RelativeLayout * parent's top edge. */ public static final int ALIGN_PARENT_TOP = 10; /** * Rule that aligns the child's right edge with its RelativeLayout * parent's right edge. */ public static final int ALIGN_PARENT_RIGHT = 11; /** * Rule that aligns the child's bottom edge with its RelativeLayout * parent's bottom edge. */ public static final int ALIGN_PARENT_BOTTOM = 12; /** * Rule that centers the child with respect to the bounds of its * RelativeLayout parent. */ public static final int CENTER_IN_PARENT = 13; /** * Rule that centers the child horizontally with respect to the * bounds of its RelativeLayout parent. */ public static final int CENTER_HORIZONTAL = 14; /** * Rule that centers the child vertically with respect to the * bounds of its RelativeLayout parent. */ public static final int CENTER_VERTICAL = 15; /** * Rule that aligns a child's end edge with another child's start edge. */ public static final int START_OF = 16; /** * Rule that aligns a child's start edge with another child's end edge. */ public static final int END_OF = 17; /** * Rule that aligns a child's start edge with another child's start edge. */ public static final int ALIGN_START = 18; /** * Rule that aligns a child's end edge with another child's end edge. */ public static final int ALIGN_END = 19; /** * Rule that aligns the child's start edge with its RelativeLayout * parent's start edge. */ public static final int ALIGN_PARENT_START = 20; /** * Rule that aligns the child's end edge with its RelativeLayout * parent's end edge. */ public static final int ALIGN_PARENT_END = 21; private static final int VERB_COUNT = 22; private static final int[] RULES_VERTICAL = { ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM }; private static final int[] RULES_HORIZONTAL = { LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END }; /** * Used to indicate left/right/top/bottom should be inferred from constraints */ private static final int VALUE_NOT_SET = Integer.MIN_VALUE; private View mBaselineView = null; @UnsupportedAppUsage private int mGravity = Gravity.START | Gravity.TOP; private final Rect mContentBounds = new Rect(); private final Rect mSelfBounds = new Rect(); private int mIgnoreGravity; private SortedSet<View> mTopToBottomLeftToRightSet = null; private boolean mDirtyHierarchy; private View[] mSortedHorizontalChildren; private View[] mSortedVerticalChildren; private final DependencyGraph mGraph = new DependencyGraph(); // Compatibility hack. Old versions of the platform had problems // with MeasureSpec value overflow and RelativeLayout was one source of them. // Some apps came to rely on them. :( private boolean mAllowBrokenMeasureSpecs = false; // Compatibility hack. Old versions of the platform would not take // margins and padding into account when generating the height measure spec // for children during the horizontal measure pass. private boolean mMeasureVerticalWithPaddingMargin = false; // A default width used for RTL measure pass /** * Value reduced so as not to interfere with View's measurement spec. flags. See: * {@link View#MEASURED_SIZE_MASK}. * {@link View#MEASURED_STATE_TOO_SMALL}. **/ private static final int DEFAULT_WIDTH = 0x00010000; public RelativeLayout(Context context) { this(context, null); } public RelativeLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initFromAttributes(context, attrs, defStyleAttr, defStyleRes); queryCompatibilityModes(context); } private void initFromAttributes(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RelativeLayout, defStyleAttr, defStyleRes); mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, View.NO_ID); mGravity = a.getInt(R.styleable.RelativeLayout_gravity, mGravity); a.recycle(); } private void queryCompatibilityModes(Context context) { int version = context.getApplicationInfo().targetSdkVersion; mAllowBrokenMeasureSpecs = version <= Build.VERSION_CODES.JELLY_BEAN_MR1; mMeasureVerticalWithPaddingMargin = version >= Build.VERSION_CODES.JELLY_BEAN_MR2; } @Override public boolean shouldDelayChildPressedState() { return false; } /** * Defines which View is ignored when the gravity is applied. This setting has no * effect if the gravity is <code>Gravity.START | Gravity.TOP</code>. * * @param viewId The id of the View to be ignored by gravity, or 0 if no View * should be ignored. * * @see #setGravity(int) * * @attr ref android.R.styleable#RelativeLayout_ignoreGravity */ @android.view.RemotableViewMethod public void setIgnoreGravity(int viewId) { mIgnoreGravity = viewId; } /** * Describes how the child views are positioned. * * @return the gravity. * * @see #setGravity(int) * @see android.view.Gravity * * @attr ref android.R.styleable#RelativeLayout_gravity */ public int getGravity() { return mGravity; } /** * Describes how the child views are positioned. Defaults to * <code>Gravity.START | Gravity.TOP</code>. * * <p>Note that since RelativeLayout considers the positioning of each child * relative to one another to be significant, setting gravity will affect * the positioning of all children as a single unit within the parent. * This happens after children have been relatively positioned.</p> * * @param gravity See {@link android.view.Gravity} * * @see #setHorizontalGravity(int) * @see #setVerticalGravity(int) * * @attr ref android.R.styleable#RelativeLayout_gravity */ @android.view.RemotableViewMethod public void setGravity(int gravity) { if (mGravity != gravity) { if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { gravity |= Gravity.START; } if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { gravity |= Gravity.TOP; } mGravity = gravity; requestLayout(); } } @android.view.RemotableViewMethod public void setHorizontalGravity(int horizontalGravity) { final int gravity = horizontalGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; if ((mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) { mGravity = (mGravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity; requestLayout(); } } @android.view.RemotableViewMethod public void setVerticalGravity(int verticalGravity) { final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK; if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) { mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity; requestLayout(); } } @Override public int getBaseline() { return mBaselineView != null ? mBaselineView.getBaseline() : super.getBaseline(); } @Override public void requestLayout() { super.requestLayout(); mDirtyHierarchy = true; } 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); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mDirtyHierarchy) { mDirtyHierarchy = false; sortChildren(); } 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; } 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; } } } views = mSortedVerticalChildren; count = views.length; final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; 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); } } } // Use the top-start-most laid out view as the baseline. RTL offsets are // applied later, so we can use the left-most edge as the starting edge. View baselineView = null; LayoutParams baselineParams = null; for (int i = 0; i < count; i++) { final View child = views[i]; if (child.getVisibility() != GONE) { final LayoutParams childParams = (LayoutParams) child.getLayoutParams(); if (baselineView == null || baselineParams == null || compareLayoutPosition(childParams, baselineParams) < 0) { baselineView = child; baselineParams = childParams; } } } mBaselineView = baselineView; 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; } } } } } if (horizontalGravity || verticalGravity) { final Rect selfBounds = mSelfBounds; selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight, height - mPaddingBottom); final Rect contentBounds = mContentBounds; Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds, layoutDirection); final int horizontalOffset = contentBounds.left - left; final int verticalOffset = contentBounds.top - top; if (horizontalOffset != 0 || verticalOffset != 0) { for (int i = 0; i < count; i++) { final View child = views[i]; if (child.getVisibility() != GONE && child != ignore) { final LayoutParams params = (LayoutParams) child.getLayoutParams(); if (horizontalGravity) { params.mLeft += horizontalOffset; params.mRight += horizontalOffset; } if (verticalGravity) { params.mTop += verticalOffset; params.mBottom += verticalOffset; } } } } } 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); } /** * @return a negative number if the top of {@code p1} is above the top of * {@code p2} or if they have identical top values and the left of * {@code p1} is to the left of {@code p2}, or a positive number * otherwise */ private int compareLayoutPosition(LayoutParams p1, LayoutParams p2) { final int topDiff = p1.mTop - p2.mTop; if (topDiff != 0) { return topDiff; } return p1.mLeft - p2.mLeft; } /** * Measure a child. The child should have left, top, right and bottom information * stored in its LayoutParams. If any of these values is VALUE_NOT_SET it means * that the view can extend up to the corresponding edge. * * @param child Child to measure * @param params LayoutParams associated with child * @param myWidth Width of the the RelativeLayout * @param myHeight Height of the RelativeLayout */ private void measureChild(View child, LayoutParams params, int myWidth, int myHeight) { int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft, params.mRight, params.width, params.leftMargin, params.rightMargin, mPaddingLeft, mPaddingRight, myWidth); int childHeightMeasureSpec = getChildMeasureSpec(params.mTop, params.mBottom, params.height, params.topMargin, params.bottomMargin, mPaddingTop, mPaddingBottom, myHeight); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } 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); } /** * Get a measure spec that accounts for all of the constraints on this view. * This includes size constraints imposed by the RelativeLayout as well as * the View's desired dimension. * * @param childStart The left or top field of the child's layout params * @param childEnd The right or bottom field of the child's layout params * @param childSize The child's desired size (the width or height field of * the child's layout params) * @param startMargin The left or top margin * @param endMargin The right or bottom margin * @param startPadding mPaddingLeft or mPaddingTop * @param endPadding mPaddingRight or mPaddingBottom * @param mySize The width or height of this view (the RelativeLayout) * @return MeasureSpec for the child */ private int getChildMeasureSpec(int childStart, int childEnd, int childSize, int startMargin, int endMargin, int startPadding, int endPadding, int mySize) { int childSpecMode = 0; int childSpecSize = 0; // Negative values in a mySize value in RelativeLayout // measurement is code for, "we got an unspecified mode in the // RelativeLayout's measure spec." final boolean isUnspecified = mySize < 0; if (isUnspecified && !mAllowBrokenMeasureSpecs) { if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) { // Constraints fixed both edges, so child has an exact size. childSpecSize = Math.max(0, childEnd - childStart); childSpecMode = MeasureSpec.EXACTLY; } else if (childSize >= 0) { // The child specified an exact size. childSpecSize = childSize; childSpecMode = MeasureSpec.EXACTLY; } else { // Allow the child to be whatever size it wants. childSpecSize = 0; childSpecMode = MeasureSpec.UNSPECIFIED; } return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode); } // Figure out start and end bounds. int tempStart = childStart; int tempEnd = childEnd; // If the view did not express a layout constraint for an edge, use // view's margins and our padding if (tempStart == VALUE_NOT_SET) { tempStart = startPadding + startMargin; } if (tempEnd == VALUE_NOT_SET) { tempEnd = mySize - endPadding - endMargin; } // Figure out maximum size available to this view final int maxAvailable = tempEnd - tempStart; if (childStart != VALUE_NOT_SET && childEnd != VALUE_NOT_SET) { // Constraints fixed both edges, so child must be an exact size. childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY; childSpecSize = Math.max(0, maxAvailable); } else { if (childSize >= 0) { // Child wanted an exact size. Give as much as possible. childSpecMode = MeasureSpec.EXACTLY; if (maxAvailable >= 0) { // We have a maximum size in this dimension. childSpecSize = Math.min(maxAvailable, childSize); } else { // We can grow in this dimension. childSpecSize = childSize; } } else if (childSize == LayoutParams.MATCH_PARENT) { // Child wanted to be as big as possible. Give all available // space. childSpecMode = isUnspecified ? MeasureSpec.UNSPECIFIED : MeasureSpec.EXACTLY; childSpecSize = Math.max(0, maxAvailable); } else if (childSize == LayoutParams.WRAP_CONTENT) { // Child wants to wrap content. Use AT_MOST to communicate // available space if we know our max size. if (maxAvailable >= 0) { // We have a maximum size in this dimension. childSpecMode = MeasureSpec.AT_MOST; childSpecSize = maxAvailable; } else { // We can grow in this dimension. Child can be as big as it // wants. childSpecMode = MeasureSpec.UNSPECIFIED; childSpecSize = 0; } } } return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode); } 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; } private void positionAtEdge(View child, LayoutParams params, int myWidth) { if (isLayoutRtl()) { params.mRight = myWidth - mPaddingRight - params.rightMargin; params.mLeft = params.mRight - child.getMeasuredWidth(); } else { params.mLeft = mPaddingLeft + params.leftMargin; params.mRight = params.mLeft + child.getMeasuredWidth(); } } private boolean positionChildVertical(View child, LayoutParams params, int myHeight, boolean wrapContent) { int[] rules = params.getRules(); if (params.mTop == VALUE_NOT_SET && params.mBottom != VALUE_NOT_SET) { // Bottom is fixed, but top varies params.mTop = params.mBottom - child.getMeasuredHeight(); } else if (params.mTop != VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) { // Top is fixed, but bottom varies params.mBottom = params.mTop + child.getMeasuredHeight(); } else if (params.mTop == VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) { // Both top and bottom vary if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) { if (!wrapContent) { centerVertical(child, params, myHeight); } else { params.mTop = mPaddingTop + params.topMargin; params.mBottom = params.mTop + child.getMeasuredHeight(); } return true; } else { params.mTop = mPaddingTop + params.topMargin; params.mBottom = params.mTop + child.getMeasuredHeight(); } } return rules[ALIGN_PARENT_BOTTOM] != 0; } 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; } } } private void applyVerticalSizeRules(LayoutParams childParams, int myHeight, int myBaseline) { final int[] rules = childParams.getRules(); // Baseline alignment overrides any explicitly specified top or bottom. int baselineOffset = getRelatedViewBaselineOffset(rules); if (baselineOffset != -1) { if (myBaseline != -1) { baselineOffset -= myBaseline; } childParams.mTop = baselineOffset; childParams.mBottom = VALUE_NOT_SET; return; } RelativeLayout.LayoutParams anchorParams; childParams.mTop = VALUE_NOT_SET; childParams.mBottom = VALUE_NOT_SET; anchorParams = getRelatedViewParams(rules, ABOVE); if (anchorParams != null) { childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin + childParams.bottomMargin); } else if (childParams.alignWithParent && rules[ABOVE] != 0) { if (myHeight >= 0) { childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin; } } anchorParams = getRelatedViewParams(rules, BELOW); if (anchorParams != null) { childParams.mTop = anchorParams.mBottom + (anchorParams.bottomMargin + childParams.topMargin); } else if (childParams.alignWithParent && rules[BELOW] != 0) { childParams.mTop = mPaddingTop + childParams.topMargin; } anchorParams = getRelatedViewParams(rules, ALIGN_TOP); if (anchorParams != null) { childParams.mTop = anchorParams.mTop + childParams.topMargin; } else if (childParams.alignWithParent && rules[ALIGN_TOP] != 0) { childParams.mTop = mPaddingTop + childParams.topMargin; } anchorParams = getRelatedViewParams(rules, ALIGN_BOTTOM); if (anchorParams != null) { childParams.mBottom = anchorParams.mBottom - childParams.bottomMargin; } else if (childParams.alignWithParent && rules[ALIGN_BOTTOM] != 0) { if (myHeight >= 0) { childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin; } } if (0 != rules[ALIGN_PARENT_TOP]) { childParams.mTop = mPaddingTop + childParams.topMargin; } if (0 != rules[ALIGN_PARENT_BOTTOM]) { if (myHeight >= 0) { childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin; } } } 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; } 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; } private int getRelatedViewBaselineOffset(int[] rules) { final View v = getRelatedView(rules, ALIGN_BASELINE); if (v != null) { final int baseline = v.getBaseline(); if (baseline != -1) { final ViewGroup.LayoutParams params = v.getLayoutParams(); if (params instanceof LayoutParams) { final LayoutParams anchorParams = (LayoutParams) v.getLayoutParams(); return anchorParams.mTop + baseline; } } } return -1; } private static void centerHorizontal(View child, LayoutParams params, int myWidth) { int childWidth = child.getMeasuredWidth(); int left = (myWidth - childWidth) / 2; params.mLeft = left; params.mRight = left + childWidth; } private static void centerVertical(View child, LayoutParams params, int myHeight) { int childHeight = child.getMeasuredHeight(); int top = (myHeight - childHeight) / 2; params.mTop = top; params.mBottom = top + childHeight; } @Override 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); } } } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new RelativeLayout.LayoutParams(getContext(), attrs); } /** * Returns a set of layout parameters with a width of * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning. */ @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } // Override to allow type-checking of LayoutParams. @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof RelativeLayout.LayoutParams; } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { if (sPreserveMarginParamsInLayoutParamConversion) { if (lp instanceof LayoutParams) { return new LayoutParams((LayoutParams) lp); } else if (lp instanceof MarginLayoutParams) { return new LayoutParams((MarginLayoutParams) lp); } } return new LayoutParams(lp); } /** @hide */ @Override public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { if (mTopToBottomLeftToRightSet == null) { mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator()); } // sort children top-to-bottom and left-to-right for (int i = 0, count = getChildCount(); i < count; i++) { mTopToBottomLeftToRightSet.add(getChildAt(i)); } for (View view : mTopToBottomLeftToRightSet) { if (view.getVisibility() == View.VISIBLE && view.dispatchPopulateAccessibilityEvent(event)) { mTopToBottomLeftToRightSet.clear(); return true; } } mTopToBottomLeftToRightSet.clear(); return false; } @Override public CharSequence getAccessibilityClassName() { return RelativeLayout.class.getName(); } /** * Compares two views in left-to-right and top-to-bottom fashion. */ private class TopToBottomLeftToRightComparator implements Comparator<View> { public int compare(View first, View second) { // top - bottom int topDifference = first.getTop() - second.getTop(); if (topDifference != 0) { return topDifference; } // left - right int leftDifference = first.getLeft() - second.getLeft(); if (leftDifference != 0) { return leftDifference; } // break tie by height int heightDiference = first.getHeight() - second.getHeight(); if (heightDiference != 0) { return heightDiference; } // break tie by width int widthDiference = first.getWidth() - second.getWidth(); if (widthDiference != 0) { return widthDiference; } return 0; } } /** * Specifies how a view is positioned within a {@link RelativeLayout}. * The relative layout containing the view uses the value of these layout parameters to * determine where to position the view on the screen. If the view is not contained * within a relative layout, these attributes are ignored. * * See the <a href="/guide/topics/ui/layout/relative.html"> * Relative Layout</a> guide for example code demonstrating how to use relative layouts * layout parameters in a layout XML. * * To learn more about layout parameters and how they differ from typical view attributes, * see the <a href="/guide/topics/ui/declaring-layout.html#attributes"> * Layouts guide</a>. * * * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignWithParentIfMissing * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toLeftOf * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toRightOf * @attr ref android.R.styleable#RelativeLayout_Layout_layout_above * @attr ref android.R.styleable#RelativeLayout_Layout_layout_below * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBaseline * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignLeft * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignTop * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignRight * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBottom * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentLeft * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentTop * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentRight * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentBottom * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toStartOf * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toEndOf * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignStart * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignEnd * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentStart * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentEnd */ public static class LayoutParams extends ViewGroup.MarginLayoutParams { @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = { @ViewDebug.IntToString(from = ABOVE, to = "above"), @ViewDebug.IntToString(from = ALIGN_BASELINE, to = "alignBaseline"), @ViewDebug.IntToString(from = ALIGN_BOTTOM, to = "alignBottom"), @ViewDebug.IntToString(from = ALIGN_LEFT, to = "alignLeft"), @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"), @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT, to = "alignParentLeft"), @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT, to = "alignParentRight"), @ViewDebug.IntToString(from = ALIGN_PARENT_TOP, to = "alignParentTop"), @ViewDebug.IntToString(from = ALIGN_RIGHT, to = "alignRight"), @ViewDebug.IntToString(from = ALIGN_TOP, to = "alignTop"), @ViewDebug.IntToString(from = BELOW, to = "below"), @ViewDebug.IntToString(from = CENTER_HORIZONTAL, to = "centerHorizontal"), @ViewDebug.IntToString(from = CENTER_IN_PARENT, to = "center"), @ViewDebug.IntToString(from = CENTER_VERTICAL, to = "centerVertical"), @ViewDebug.IntToString(from = LEFT_OF, to = "leftOf"), @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf"), @ViewDebug.IntToString(from = ALIGN_START, to = "alignStart"), @ViewDebug.IntToString(from = ALIGN_END, to = "alignEnd"), @ViewDebug.IntToString(from = ALIGN_PARENT_START, to = "alignParentStart"), @ViewDebug.IntToString(from = ALIGN_PARENT_END, to = "alignParentEnd"), @ViewDebug.IntToString(from = START_OF, to = "startOf"), @ViewDebug.IntToString(from = END_OF, to = "endOf") }, mapping = { @ViewDebug.IntToString(from = TRUE, to = "true"), @ViewDebug.IntToString(from = 0, to = "false/NO_ID") }) private int[] mRules = new int[VERB_COUNT]; private int[] mInitialRules = new int[VERB_COUNT]; @UnsupportedAppUsage private int mLeft; @UnsupportedAppUsage private int mTop; @UnsupportedAppUsage private int mRight; @UnsupportedAppUsage private int mBottom; /** * Whether this view had any relative rules modified following the most * recent resolution of layout direction. */ private boolean mNeedsLayoutResolution; private boolean mRulesChanged = false; private boolean mIsRtlCompatibilityMode = false; /** * When true, uses the parent as the anchor if the anchor doesn't exist or if * the anchor's visibility is GONE. */ @ViewDebug.ExportedProperty(category = "layout") public boolean alignWithParent; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.RelativeLayout_Layout); final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion; mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 || !c.getApplicationInfo().hasRtlSupport()); final int[] rules = mRules; //noinspection MismatchedReadAndWriteOfArray final int[] initialRules = mInitialRules; final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); switch (attr) { case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing: alignWithParent = a.getBoolean(attr, false); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf: rules[LEFT_OF] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf: rules[RIGHT_OF] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above: rules[ABOVE] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below: rules[BELOW] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline: rules[ALIGN_BASELINE] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft: rules[ALIGN_LEFT] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop: rules[ALIGN_TOP] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight: rules[ALIGN_RIGHT] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom: rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft: rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop: rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight: rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom: rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent: rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal: rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical: rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf: rules[START_OF] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf: rules[END_OF] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart: rules[ALIGN_START] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd: rules[ALIGN_END] = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart: rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0; break; case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd: rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0; break; } } mRulesChanged = true; System.arraycopy(rules, LEFT_OF, initialRules, LEFT_OF, VERB_COUNT); a.recycle(); } public LayoutParams(int w, int h) { super(w, h); } /** * {@inheritDoc} */ public LayoutParams(ViewGroup.LayoutParams source) { super(source); } /** * {@inheritDoc} */ public LayoutParams(ViewGroup.MarginLayoutParams source) { super(source); } /** * Copy constructor. Clones the width, height, margin values, and rules * of the source. * * @param source The layout params to copy from. */ public LayoutParams(LayoutParams source) { super(source); this.mIsRtlCompatibilityMode = source.mIsRtlCompatibilityMode; this.mRulesChanged = source.mRulesChanged; this.alignWithParent = source.alignWithParent; System.arraycopy(source.mRules, LEFT_OF, this.mRules, LEFT_OF, VERB_COUNT); System.arraycopy(source.mInitialRules, LEFT_OF, this.mInitialRules, LEFT_OF, VERB_COUNT); } @Override public String debug(String output) { return output + "ViewGroup.LayoutParams={ width=" + sizeToString(width) + ", height=" + sizeToString(height) + " }"; } /** * Adds a layout rule to be interpreted by the RelativeLayout. * <p> * This method should only be used for verbs that don't refer to a * sibling (ex. {@link #ALIGN_RIGHT}) or take a boolean * value ({@link #TRUE} for true or 0 for false). To * specify a verb that takes a subject, use {@link #addRule(int, int)}. * <p> * If the rule is relative to the layout direction (ex. * {@link #ALIGN_PARENT_START}), then the layout direction must be * resolved using {@link #resolveLayoutDirection(int)} before calling * {@link #getRule(int)} an absolute rule (ex. * {@link #ALIGN_PARENT_LEFT}. * * @param verb a layout verb, such as {@link #ALIGN_PARENT_LEFT} * @see #addRule(int, int) * @see #removeRule(int) * @see #getRule(int) */ public void addRule(int verb) { addRule(verb, TRUE); } /** * Adds a layout rule to be interpreted by the RelativeLayout. * <p> * Use this for verbs that refer to a sibling (ex. * {@link #ALIGN_RIGHT}) or take a boolean value (ex. * {@link #CENTER_IN_PARENT}). * <p> * If the rule is relative to the layout direction (ex. * {@link #START_OF}), then the layout direction must be resolved using * {@link #resolveLayoutDirection(int)} before calling * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}. * * @param verb a layout verb, such as {@link #ALIGN_RIGHT} * @param subject the ID of another view to use as an anchor, or a * boolean value (represented as {@link #TRUE} for true * or 0 for false) * @see #addRule(int) * @see #removeRule(int) * @see #getRule(int) */ public void addRule(int verb, int subject) { // If we're removing a relative rule, we'll need to force layout // resolution the next time it's requested. if (!mNeedsLayoutResolution && isRelativeRule(verb) && mInitialRules[verb] != 0 && subject == 0) { mNeedsLayoutResolution = true; } mRules[verb] = subject; mInitialRules[verb] = subject; mRulesChanged = true; } /** * Removes a layout rule to be interpreted by the RelativeLayout. * <p> * If the rule is relative to the layout direction (ex. * {@link #START_OF}, {@link #ALIGN_PARENT_START}, etc.) then the * layout direction must be resolved using * {@link #resolveLayoutDirection(int)} before before calling * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}. * * @param verb One of the verbs defined by * {@link android.widget.RelativeLayout RelativeLayout}, such as * ALIGN_WITH_PARENT_LEFT. * @see #addRule(int) * @see #addRule(int, int) * @see #getRule(int) */ public void removeRule(int verb) { addRule(verb, 0); } /** * Returns the layout rule associated with a specific verb. * * @param verb one of the verbs defined by {@link RelativeLayout}, such * as ALIGN_WITH_PARENT_LEFT * @return the id of another view to use as an anchor, a boolean value * (represented as {@link RelativeLayout#TRUE} for true * or 0 for false), or -1 for verbs that don't refer to another * sibling (for example, ALIGN_WITH_PARENT_BOTTOM) * @see #addRule(int) * @see #addRule(int, int) */ public int getRule(int verb) { return mRules[verb]; } private boolean hasRelativeRules() { return (mInitialRules[START_OF] != 0 || mInitialRules[END_OF] != 0 || mInitialRules[ALIGN_START] != 0 || mInitialRules[ALIGN_END] != 0 || mInitialRules[ALIGN_PARENT_START] != 0 || mInitialRules[ALIGN_PARENT_END] != 0); } private boolean isRelativeRule(int rule) { return rule == START_OF || rule == END_OF || rule == ALIGN_START || rule == ALIGN_END || rule == ALIGN_PARENT_START || rule == ALIGN_PARENT_END; } // The way we are resolving rules depends on the layout direction and if we are pre JB MR1 // or not. // // If we are pre JB MR1 (said as "RTL compatibility mode"), "left"/"right" rules are having // predominance over any "start/end" rules that could have been defined. A special case: // if no "left"/"right" rule has been defined and "start"/"end" rules are defined then we // resolve those "start"/"end" rules to "left"/"right" respectively. // // If we are JB MR1+, then "start"/"end" rules are having predominance over "left"/"right" // rules. If no "start"/"end" rule is defined then we use "left"/"right" rules. // // In all cases, the result of the resolution should clear the "start"/"end" rules to leave // only the "left"/"right" rules at the end. private void resolveRules(int layoutDirection) { final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL); // Reset to initial state System.arraycopy(mInitialRules, LEFT_OF, mRules, LEFT_OF, VERB_COUNT); // Apply rules depending on direction and if we are in RTL compatibility mode if (mIsRtlCompatibilityMode) { if (mRules[ALIGN_START] != 0) { if (mRules[ALIGN_LEFT] == 0) { // "left" rule is not defined but "start" rule is: use the "start" rule as // the "left" rule mRules[ALIGN_LEFT] = mRules[ALIGN_START]; } mRules[ALIGN_START] = 0; } if (mRules[ALIGN_END] != 0) { if (mRules[ALIGN_RIGHT] == 0) { // "right" rule is not defined but "end" rule is: use the "end" rule as the // "right" rule mRules[ALIGN_RIGHT] = mRules[ALIGN_END]; } mRules[ALIGN_END] = 0; } if (mRules[START_OF] != 0) { if (mRules[LEFT_OF] == 0) { // "left" rule is not defined but "start" rule is: use the "start" rule as // the "left" rule mRules[LEFT_OF] = mRules[START_OF]; } mRules[START_OF] = 0; } if (mRules[END_OF] != 0) { if (mRules[RIGHT_OF] == 0) { // "right" rule is not defined but "end" rule is: use the "end" rule as the // "right" rule mRules[RIGHT_OF] = mRules[END_OF]; } mRules[END_OF] = 0; } if (mRules[ALIGN_PARENT_START] != 0) { if (mRules[ALIGN_PARENT_LEFT] == 0) { // "left" rule is not defined but "start" rule is: use the "start" rule as // the "left" rule mRules[ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START]; } mRules[ALIGN_PARENT_START] = 0; } if (mRules[ALIGN_PARENT_END] != 0) { if (mRules[ALIGN_PARENT_RIGHT] == 0) { // "right" rule is not defined but "end" rule is: use the "end" rule as the // "right" rule mRules[ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END]; } mRules[ALIGN_PARENT_END] = 0; } } else { // JB MR1+ case if ((mRules[ALIGN_START] != 0 || mRules[ALIGN_END] != 0) && (mRules[ALIGN_LEFT] != 0 || mRules[ALIGN_RIGHT] != 0)) { // "start"/"end" rules take precedence over "left"/"right" rules mRules[ALIGN_LEFT] = 0; mRules[ALIGN_RIGHT] = 0; } if (mRules[ALIGN_START] != 0) { // "start" rule resolved to "left" or "right" depending on the direction mRules[isLayoutRtl ? ALIGN_RIGHT : ALIGN_LEFT] = mRules[ALIGN_START]; mRules[ALIGN_START] = 0; } if (mRules[ALIGN_END] != 0) { // "end" rule resolved to "left" or "right" depending on the direction mRules[isLayoutRtl ? ALIGN_LEFT : ALIGN_RIGHT] = mRules[ALIGN_END]; mRules[ALIGN_END] = 0; } if ((mRules[START_OF] != 0 || mRules[END_OF] != 0) && (mRules[LEFT_OF] != 0 || mRules[RIGHT_OF] != 0)) { // "start"/"end" rules take precedence over "left"/"right" rules mRules[LEFT_OF] = 0; mRules[RIGHT_OF] = 0; } if (mRules[START_OF] != 0) { // "start" rule resolved to "left" or "right" depending on the direction mRules[isLayoutRtl ? RIGHT_OF : LEFT_OF] = mRules[START_OF]; mRules[START_OF] = 0; } if (mRules[END_OF] != 0) { // "end" rule resolved to "left" or "right" depending on the direction mRules[isLayoutRtl ? LEFT_OF : RIGHT_OF] = mRules[END_OF]; mRules[END_OF] = 0; } if ((mRules[ALIGN_PARENT_START] != 0 || mRules[ALIGN_PARENT_END] != 0) && (mRules[ALIGN_PARENT_LEFT] != 0 || mRules[ALIGN_PARENT_RIGHT] != 0)) { // "start"/"end" rules take precedence over "left"/"right" rules mRules[ALIGN_PARENT_LEFT] = 0; mRules[ALIGN_PARENT_RIGHT] = 0; } if (mRules[ALIGN_PARENT_START] != 0) { // "start" rule resolved to "left" or "right" depending on the direction mRules[isLayoutRtl ? ALIGN_PARENT_RIGHT : ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START]; mRules[ALIGN_PARENT_START] = 0; } if (mRules[ALIGN_PARENT_END] != 0) { // "end" rule resolved to "left" or "right" depending on the direction mRules[isLayoutRtl ? ALIGN_PARENT_LEFT : ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END]; mRules[ALIGN_PARENT_END] = 0; } } mRulesChanged = false; mNeedsLayoutResolution = false; } /** * Retrieves a complete list of all supported rules, where the index is the rule * verb, and the element value is the value specified, or "false" if it was never * set. If there are relative rules defined (*_START / *_END), they will be resolved * depending on the layout direction. * * @param layoutDirection the direction of the layout. * Should be either {@link View#LAYOUT_DIRECTION_LTR} * or {@link View#LAYOUT_DIRECTION_RTL} * @return the supported rules * @see #addRule(int, int) * * @hide */ public int[] getRules(int layoutDirection) { resolveLayoutDirection(layoutDirection); return mRules; } /** * Retrieves a complete list of all supported rules, where the index is the rule * verb, and the element value is the value specified, or "false" if it was never * set. There will be no resolution of relative rules done. * * @return the supported rules * @see #addRule(int, int) */ public int[] getRules() { return mRules; } /** * This will be called by {@link android.view.View#requestLayout()} to * resolve layout parameters that are relative to the layout direction. * <p> * After this method is called, any rules using layout-relative verbs * (ex. {@link #START_OF}) previously added via {@link #addRule(int)} * may only be accessed via their resolved absolute verbs (ex. * {@link #LEFT_OF}). */ @Override public void resolveLayoutDirection(int layoutDirection) { if (shouldResolveLayoutDirection(layoutDirection)) { resolveRules(layoutDirection); } // This will set the layout direction. super.resolveLayoutDirection(layoutDirection); } private boolean shouldResolveLayoutDirection(int layoutDirection) { return (mNeedsLayoutResolution || hasRelativeRules()) && (mRulesChanged || layoutDirection != getLayoutDirection()); } /** @hide */ @Override protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) { super.encodeProperties(encoder); encoder.addProperty("layout:alignWithParent", alignWithParent); } } 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>(); /** * Clears the graph. */ void clear() { final ArrayList<Node> nodes = mNodes; final int count = nodes.size(); for (int i = 0; i < count; i++) { nodes.get(i).release(); } nodes.clear(); mKeyNodes.clear(); mRoots.clear(); } /** * Adds a view to the graph. * * @param view The view to be added as a node to the graph. */ void add(View view) { final int id = view.getId(); final Node node = Node.acquire(view); if (id != View.NO_ID) { mKeyNodes.put(id, node); } mNodes.add(node); } /** * Builds a sorted list of views. The sorting order depends on the dependencies * between the view. For instance, if view C needs view A to be processed first * and view A needs view B to be processed first, the dependency graph * is: B -> A -> C. The sorted array will contain views B, A and C in this order. * * @param sorted The sorted list of views. The length of this array must * be equal to getChildCount(). * @param rules The list of rules to take into account. */ void getSortedViews(View[] sorted, int... rules) { final ArrayDeque<Node> roots = findRoots(rules); int index = 0; Node node; while ((node = roots.pollLast()) != null) { final View view = node.view; final int key = view.getId(); sorted[index++] = view; final ArrayMap<Node, DependencyGraph> dependents = node.dependents; final int count = dependents.size(); for (int i = 0; i < count; i++) { final Node dependent = dependents.keyAt(i); final SparseArray<Node> dependencies = dependent.dependencies; dependencies.remove(key); if (dependencies.size() == 0) { roots.add(dependent); } } } if (index < sorted.length) { throw new IllegalStateException("Circular dependencies cannot exist" + " in RelativeLayout"); } } /** * Finds the roots of the graph. A root is a node with no dependency and * with [0..n] dependents. * * @param rulesFilter The list of rules to consider when building the * dependencies * * @return A list of node, each being a root of the graph */ private ArrayDeque<Node> findRoots(int[] rulesFilter) { final SparseArray<Node> keyNodes = mKeyNodes; final ArrayList<Node> nodes = mNodes; final int count = nodes.size(); // Find roots can be invoked several times, so make sure to clear // all dependents and dependencies before running the algorithm for (int i = 0; i < count; i++) { final Node node = nodes.get(i); node.dependents.clear(); node.dependencies.clear(); } // Builds up the dependents and dependencies for each node of the graph for (int i = 0; i < count; i++) { final Node node = nodes.get(i); final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams(); final int[] rules = layoutParams.mRules; final int rulesCount = rulesFilter.length; // Look only the the rules passed in parameter, this way we build only the // dependencies for a specific set of rules for (int j = 0; j < rulesCount; j++) { final int rule = rules[rulesFilter[j]]; if (rule > 0) { // The node this node depends on final Node dependency = keyNodes.get(rule); // Skip unknowns and self dependencies if (dependency == null || dependency == node) { continue; } // Add the current node as a dependent dependency.dependents.put(node, this); // Add a dependency to the current node node.dependencies.put(rule, dependency); } } } final ArrayDeque<Node> roots = mRoots; roots.clear(); // Finds all the roots in the graph: all nodes with no dependencies for (int i = 0; i < count; i++) { final Node node = nodes.get(i); if (node.dependencies.size() == 0) roots.addLast(node); } return roots; } /** * A node in the dependency graph. A node is a view, its list of dependencies * and its list of dependents. * * A node with no dependent is considered a root of the graph. */ 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>(); /* * START POOL IMPLEMENTATION */ // The pool is static, so all nodes instances are shared across // activities, that's why we give it a rather high limit private static final int POOL_LIMIT = 100; private static final SynchronizedPool<Node> sPool = new SynchronizedPool<Node>(POOL_LIMIT); static Node acquire(View view) { Node node = sPool.acquire(); if (node == null) { node = new Node(); } node.view = view; return node; } void release() { view = null; dependents.clear(); dependencies.clear(); sPool.release(this); } /* * END POOL IMPLEMENTATION */ } } }