2082 lines
86 KiB
Java
2082 lines
86 KiB
Java
/*
|
|
* 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.compat.annotation.UnsupportedAppUsage;
|
|
import android.content.Context;
|
|
import android.content.res.ResourceId;
|
|
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.view.inspector.InspectableProperty;
|
|
import android.view.inspector.InspectionCompanion;
|
|
import android.view.inspector.PropertyMapper;
|
|
import android.view.inspector.PropertyReader;
|
|
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(maxTargetSdk = Build.VERSION_CODES.P)
|
|
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);
|
|
saveAttributeDataForStyleable(context, R.styleable.RelativeLayout,
|
|
attrs, a, 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;
|
|
}
|
|
|
|
/**
|
|
* Get the id of the View to be ignored by gravity
|
|
*
|
|
* @attr ref android.R.styleable#RelativeLayout_ignoreGravity
|
|
*/
|
|
@InspectableProperty
|
|
public int getIgnoreGravity() {
|
|
return mIgnoreGravity;
|
|
}
|
|
|
|
/**
|
|
* Describes how the child views are positioned.
|
|
*
|
|
* @return the gravity.
|
|
*
|
|
* @see #setGravity(int)
|
|
* @see android.view.Gravity
|
|
*
|
|
* @attr ref android.R.styleable#RelativeLayout_gravity
|
|
*/
|
|
@InspectableProperty(valueType = InspectableProperty.ValueType.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="{@docRoot}guide/topics/ui/layout/relative.html">Relative
|
|
* Layout</a> guide for example code demonstrating how to use relative layout's
|
|
* layout parameters in a layout XML.
|
|
*
|
|
* To learn more about layout parameters and how they differ from typical view attributes,
|
|
* see the <a href="{@docRoot}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);
|
|
}
|
|
|
|
/** @hide */
|
|
public static final class InspectionCompanion
|
|
implements android.view.inspector.InspectionCompanion<LayoutParams> {
|
|
private boolean mPropertiesMapped;
|
|
|
|
private int mAboveId;
|
|
private int mAlignBaselineId;
|
|
private int mAlignBottomId;
|
|
private int mAlignEndId;
|
|
private int mAlignLeftId;
|
|
private int mAlignParentBottomId;
|
|
private int mAlignParentEndId;
|
|
private int mAlignParentLeftId;
|
|
private int mAlignParentRightId;
|
|
private int mAlignParentStartId;
|
|
private int mAlignParentTopId;
|
|
private int mAlignRightId;
|
|
private int mAlignStartId;
|
|
private int mAlignTopId;
|
|
private int mAlignWithParentIfMissingId;
|
|
private int mBelowId;
|
|
private int mCenterHorizontalId;
|
|
private int mCenterInParentId;
|
|
private int mCenterVerticalId;
|
|
private int mToEndOfId;
|
|
private int mToLeftOfId;
|
|
private int mToRightOfId;
|
|
private int mToStartOfId;
|
|
|
|
@Override
|
|
public void mapProperties(@NonNull PropertyMapper propertyMapper) {
|
|
mPropertiesMapped = true;
|
|
|
|
mAboveId = propertyMapper.mapResourceId("layout_above", R.attr.layout_above);
|
|
|
|
mAlignBaselineId = propertyMapper.mapResourceId(
|
|
"layout_alignBaseline", R.attr.layout_alignBaseline);
|
|
|
|
mAlignBottomId = propertyMapper.mapResourceId(
|
|
"layout_alignBottom", R.attr.layout_alignBottom);
|
|
|
|
mAlignEndId = propertyMapper.mapResourceId(
|
|
"layout_alignEnd", R.attr.layout_alignEnd);
|
|
|
|
mAlignLeftId = propertyMapper.mapResourceId(
|
|
"layout_alignLeft", R.attr.layout_alignLeft);
|
|
|
|
mAlignParentBottomId = propertyMapper.mapBoolean(
|
|
"layout_alignParentBottom", R.attr.layout_alignParentBottom);
|
|
|
|
mAlignParentEndId = propertyMapper.mapBoolean(
|
|
"layout_alignParentEnd", R.attr.layout_alignParentEnd);
|
|
|
|
mAlignParentLeftId = propertyMapper.mapBoolean(
|
|
"layout_alignParentLeft", R.attr.layout_alignParentLeft);
|
|
|
|
mAlignParentRightId = propertyMapper.mapBoolean(
|
|
"layout_alignParentRight", R.attr.layout_alignParentRight);
|
|
|
|
mAlignParentStartId = propertyMapper.mapBoolean(
|
|
"layout_alignParentStart", R.attr.layout_alignParentStart);
|
|
|
|
mAlignParentTopId = propertyMapper.mapBoolean(
|
|
"layout_alignParentTop", R.attr.layout_alignParentTop);
|
|
|
|
mAlignRightId = propertyMapper.mapResourceId(
|
|
"layout_alignRight", R.attr.layout_alignRight);
|
|
|
|
mAlignStartId = propertyMapper.mapResourceId(
|
|
"layout_alignStart", R.attr.layout_alignStart);
|
|
|
|
mAlignTopId = propertyMapper.mapResourceId(
|
|
"layout_alignTop", R.attr.layout_alignTop);
|
|
|
|
mAlignWithParentIfMissingId = propertyMapper.mapBoolean(
|
|
"layout_alignWithParentIfMissing",
|
|
R.attr.layout_alignWithParentIfMissing);
|
|
|
|
mBelowId = propertyMapper.mapResourceId("layout_below", R.attr.layout_below);
|
|
|
|
mCenterHorizontalId = propertyMapper.mapBoolean(
|
|
"layout_centerHorizontal", R.attr.layout_centerHorizontal);
|
|
|
|
mCenterInParentId = propertyMapper.mapBoolean(
|
|
"layout_centerInParent", R.attr.layout_centerInParent);
|
|
|
|
mCenterVerticalId = propertyMapper.mapBoolean(
|
|
"layout_centerVertical", R.attr.layout_centerVertical);
|
|
|
|
mToEndOfId = propertyMapper.mapResourceId(
|
|
"layout_toEndOf", R.attr.layout_toEndOf);
|
|
|
|
mToLeftOfId = propertyMapper.mapResourceId(
|
|
"layout_toLeftOf", R.attr.layout_toLeftOf);
|
|
|
|
mToRightOfId = propertyMapper.mapResourceId(
|
|
"layout_toRightOf", R.attr.layout_toRightOf);
|
|
|
|
mToStartOfId = propertyMapper.mapResourceId(
|
|
"layout_toStartOf", R.attr.layout_toStartOf);
|
|
}
|
|
|
|
@Override
|
|
public void readProperties(
|
|
@NonNull LayoutParams node,
|
|
@NonNull PropertyReader propertyReader
|
|
) {
|
|
if (!mPropertiesMapped) {
|
|
throw new UninitializedPropertyMapException();
|
|
}
|
|
|
|
final int[] rules = node.getRules();
|
|
|
|
propertyReader.readResourceId(mAboveId, rules[ABOVE]);
|
|
propertyReader.readResourceId(mAlignBaselineId, rules[ALIGN_BASELINE]);
|
|
propertyReader.readResourceId(mAlignBottomId, rules[ALIGN_BOTTOM]);
|
|
propertyReader.readResourceId(mAlignEndId, rules[ALIGN_END]);
|
|
propertyReader.readResourceId(mAlignLeftId, rules[ALIGN_LEFT]);
|
|
propertyReader.readBoolean(
|
|
mAlignParentBottomId, rules[ALIGN_PARENT_BOTTOM] == TRUE);
|
|
propertyReader.readBoolean(mAlignParentEndId, rules[ALIGN_PARENT_END] == TRUE);
|
|
propertyReader.readBoolean(mAlignParentLeftId, rules[ALIGN_PARENT_LEFT] == TRUE);
|
|
propertyReader.readBoolean(mAlignParentRightId, rules[ALIGN_PARENT_RIGHT] == TRUE);
|
|
propertyReader.readBoolean(mAlignParentStartId, rules[ALIGN_PARENT_START] == TRUE);
|
|
propertyReader.readBoolean(mAlignParentTopId, rules[ALIGN_PARENT_TOP] == TRUE);
|
|
propertyReader.readResourceId(mAlignRightId, rules[ALIGN_RIGHT]);
|
|
propertyReader.readResourceId(mAlignStartId, rules[ALIGN_START]);
|
|
propertyReader.readResourceId(mAlignTopId, rules[ALIGN_TOP]);
|
|
propertyReader.readBoolean(mAlignWithParentIfMissingId, node.alignWithParent);
|
|
propertyReader.readResourceId(mBelowId, rules[BELOW]);
|
|
propertyReader.readBoolean(mCenterHorizontalId, rules[CENTER_HORIZONTAL] == TRUE);
|
|
propertyReader.readBoolean(mCenterInParentId, rules[CENTER_IN_PARENT] == TRUE);
|
|
propertyReader.readBoolean(mCenterVerticalId, rules[CENTER_VERTICAL] == TRUE);
|
|
propertyReader.readResourceId(mToEndOfId, rules[END_OF]);
|
|
propertyReader.readResourceId(mToLeftOfId, rules[LEFT_OF]);
|
|
propertyReader.readResourceId(mToRightOfId, rules[RIGHT_OF]);
|
|
propertyReader.readResourceId(mToStartOfId, rules[START_OF]);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 || ResourceId.isValid(rule)) {
|
|
// 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 {
|
|
|
|
@UnsupportedAppUsage
|
|
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
|
|
*/
|
|
}
|
|
}
|
|
}
|