diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
tree | 35051494d2af230dce54d6b31c6af8fc24091316 /core/java/android/widget/RelativeLayout.java | |
download | frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2 |
Initial Contribution
Diffstat (limited to 'core/java/android/widget/RelativeLayout.java')
-rw-r--r-- | core/java/android/widget/RelativeLayout.java | 950 |
1 files changed, 950 insertions, 0 deletions
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java new file mode 100644 index 0000000..91d5805 --- /dev/null +++ b/core/java/android/widget/RelativeLayout.java @@ -0,0 +1,950 @@ +/* + * 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 android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.Gravity; +import android.widget.RemoteViews.RemoteView; +import android.graphics.Rect; +import com.android.internal.R; + + +/** + * A Layout where the positions of the children can be described in relation to each other or to the + * parent. For the sake of efficiency, the relations between views are evaluated in one pass, so if + * view Y is dependent on the position of view X, make sure the view X comes first in the layout. + * + * <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> + * 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; + + private static final int VERB_COUNT = 16; + + private View mBaselineView = null; + private boolean mHasBaselineAlignedChild; + + private int mGravity = Gravity.LEFT | Gravity.TOP; + private final Rect mContentBounds = new Rect(); + private final Rect mSelfBounds = new Rect(); + private int mIgnoreGravity; + + public RelativeLayout(Context context) { + super(context); + } + + public RelativeLayout(Context context, AttributeSet attrs) { + super(context, attrs); + initFromAttributes(context, attrs); + } + + public RelativeLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initFromAttributes(context, attrs); + } + + private void initFromAttributes(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RelativeLayout); + mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, 0); + mGravity = a.getInt(R.styleable.RelativeLayout_gravity, mGravity); + a.recycle(); + } + + /** + * Defines which View is ignored when the gravity is applied. This setting has no + * effect if the gravity is <code>Gravity.LEFT | 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 + */ + public void setIgnoreGravity(int viewId) { + mIgnoreGravity = viewId; + } + + /** + * Describes how the child views are positioned. Defaults to + * <code>Gravity.LEFT | Gravity.TOP</code>. + * + * @param gravity See {@link android.view.Gravity} + * + * @see #setHorizontalGravity(int) + * @see #setVerticalGravity(int) + * + * @attr ref android.R.styleable#RelativeLayout_gravity + */ + public void setGravity(int gravity) { + if (mGravity != gravity) { + if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) { + gravity |= Gravity.LEFT; + } + + if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { + gravity |= Gravity.TOP; + } + + mGravity = gravity; + requestLayout(); + } + } + + public void setHorizontalGravity(int horizontalGravity) { + final int gravity = horizontalGravity & Gravity.HORIZONTAL_GRAVITY_MASK; + if ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != gravity) { + mGravity = (mGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK) | gravity; + requestLayout(); + } + } + + 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 + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int myWidth = -1; + int myHeight = -1; + + int width = 0; + int height = 0; + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + 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; + } + + int len = this.getChildCount(); + mHasBaselineAlignedChild = false; + + View ignore = null; + int gravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK; + final boolean horizontalGravity = gravity != Gravity.LEFT && 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; + + if ((horizontalGravity || verticalGravity) && mIgnoreGravity != 0) { + ignore = findViewById(mIgnoreGravity); + } + + for (int i = 0; i < len; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + LayoutParams params = (LayoutParams) child.getLayoutParams(); + applySizeRules(params, myWidth, myHeight); + measureChild(child, params, myWidth, myHeight); + positionChild(child, params, myWidth, myHeight); + + if (widthMode != MeasureSpec.EXACTLY) { + width = Math.max(width, params.mRight); + } + if (heightMode != MeasureSpec.EXACTLY) { + height = Math.max(height, params.mBottom); + } + + 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); + } + } + } + + if (mHasBaselineAlignedChild) { + for (int i = 0; i < len; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE) { + LayoutParams params = (LayoutParams) child.getLayoutParams(); + alignBaseline(child, params); + + 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); + } + } + } + } + + if (widthMode != MeasureSpec.EXACTLY) { + // Width already has left padding in it since it was calculated by looking at + // the right of each child view + width += mPaddingRight; + + if (mLayoutParams.width >= 0) { + width = Math.max(width, mLayoutParams.width); + } + + width = Math.max(width, getSuggestedMinimumWidth()); + width = resolveSize(width, widthMeasureSpec); + } + if (heightMode != MeasureSpec.EXACTLY) { + // Height already has top padding in it since it was calculated by looking at + // the bottom of each child view + height += mPaddingBottom; + + if (mLayoutParams.height >= 0) { + height = Math.max(height, mLayoutParams.height); + } + + height = Math.max(height, getSuggestedMinimumHeight()); + height = resolveSize(height, heightMeasureSpec); + } + + 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); + + final int horizontalOffset = contentBounds.left - left; + final int verticalOffset = contentBounds.top - top; + if (horizontalOffset != 0 || verticalOffset != 0) { + for (int i = 0; i < len; i++) { + View child = getChildAt(i); + if (child.getVisibility() != GONE && child != ignore) { + LayoutParams params = (LayoutParams) child.getLayoutParams(); + params.mLeft += horizontalOffset; + params.mRight += horizontalOffset; + params.mTop += verticalOffset; + params.mBottom += verticalOffset; + } + } + } + } + + setMeasuredDimension(width, height); + } + + private void alignBaseline(View child, LayoutParams params) { + int[] rules = params.getRules(); + int anchorBaseline = getRelatedViewBaseline(rules, ALIGN_BASELINE); + + if (anchorBaseline != -1) { + LayoutParams anchorParams = getRelatedViewParams(rules, ALIGN_BASELINE); + if (anchorParams != null) { + int offset = anchorParams.mTop + anchorBaseline; + int baseline = child.getBaseline(); + if (baseline != -1) { + offset -= baseline; + } + int height = params.mBottom - params.mTop; + params.mTop = offset; + params.mBottom = params.mTop + height; + } + } + + if (mBaselineView == null) { + mBaselineView = child; + } else { + LayoutParams lp = (LayoutParams) mBaselineView.getLayoutParams(); + if (params.mTop < lp.mTop || (params.mTop == lp.mTop && params.mLeft < lp.mLeft)) { + mBaselineView = child; + } + } + } + + /** + * Measure a child. The child should have left, top, right and bottom information + * stored in its LayoutParams. If any of these values is -1 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); + } + + /** + * Get a measure spec that accounts for all of the constraints on this view. + * This includes size contstraints 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; + + // 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 < 0) { + tempStart = startPadding + startMargin; + } + if (tempEnd < 0) { + tempEnd = mySize - endPadding - endMargin; + } + + // Figure out maximum size available to this view + int maxAvailable = tempEnd - tempStart; + + if (childStart >= 0 && childEnd >= 0) { + // Constraints fixed both edges, so child must be an exact size + childSpecMode = MeasureSpec.EXACTLY; + childSpecSize = maxAvailable; + } else { + if (childSize >= 0) { + // Child wanted an exact size. Give as much as possible + childSpecMode = MeasureSpec.EXACTLY; + + if (maxAvailable >= 0) { + // We have a maxmum size in this dimension. + childSpecSize = Math.min(maxAvailable, childSize); + } else { + // We can grow in this dimension. + childSpecSize = childSize; + } + } else if (childSize == LayoutParams.FILL_PARENT) { + // Child wanted to be as big as possible. Give all availble + // space + childSpecMode = MeasureSpec.EXACTLY; + childSpecSize = 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 maxmum 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); + } + + /** + * After the child has been measured, assign it a position. Some views may + * already have final values for l,t,r,b. Others may have one or both edges + * unfixed (i.e. set to -1) in each dimension. These will get positioned + * based on which edge is fixed, the view's desired dimension, and whether + * or not it is centered. + * + * @param child Child to position + * @param params LayoutParams associated with child + * @param myWidth Width of the the RelativeLayout + * @param myHeight Height of the RelativeLayout + */ + private void positionChild(View child, LayoutParams params, int myWidth, int myHeight) { + int[] rules = params.getRules(); + + if (params.mLeft < 0 && params.mRight >= 0) { + // Right is fixed, but left varies + params.mLeft = params.mRight - child.getMeasuredWidth(); + } else if (params.mLeft >= 0 && params.mRight < 0) { + // Left is fixed, but right varies + params.mRight = params.mLeft + child.getMeasuredWidth(); + } else if (params.mLeft < 0 && params.mRight < 0) { + // Both left and right vary + if (0 != rules[CENTER_IN_PARENT] || 0 != rules[CENTER_HORIZONTAL]) { + centerHorizontal(child, params, myWidth); + } else { + params.mLeft = mPaddingLeft + params.leftMargin; + params.mRight = params.mLeft + child.getMeasuredWidth(); + } + } + + if (params.mTop < 0 && params.mBottom >= 0) { + // Bottom is fixed, but top varies + params.mTop = params.mBottom - child.getMeasuredHeight(); + } else if (params.mTop >= 0 && params.mBottom < 0) { + // Top is fixed, but bottom varies + params.mBottom = params.mTop + child.getMeasuredHeight(); + } else if (params.mTop < 0 && params.mBottom < 0) { + // Both top and bottom vary + if (0 != rules[CENTER_IN_PARENT] || 0 != rules[CENTER_VERTICAL]) { + centerVertical(child, params, myHeight); + } else { + params.mTop = mPaddingTop + params.topMargin; + params.mBottom = params.mTop + child.getMeasuredHeight(); + } + } + } + + /** + * Set l,t,r,b values in the LayoutParams for one view based on its layout rules. + * Big assumption #1: All antecedents of this view have been sized & positioned + * Big assumption #2: The dimensions of the parent view (the RelativeLayout) + * are already known if they are needed. + * + * @param childParams LayoutParams for the view being positioned + * @param myWidth Width of the the RelativeLayout + * @param myHeight Height of the RelativeLayout + */ + private void applySizeRules(LayoutParams childParams, int myWidth, int myHeight) { + int[] rules = childParams.getRules(); + RelativeLayout.LayoutParams anchorParams; + + // -1 indicated a "soft requirement" in that direction. For example: + // left=10, right=-1 means the view must start at 10, but can go as far as it wants to the right + // left =-1, 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 = -1; + childParams.mRight = -1; + + 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; + } else { + // FIXME uh oh... + } + } + + 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; + } else { + // FIXME uh oh... + } + } + + 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; + } else { + // FIXME uh oh... + } + } + + childParams.mTop = -1; + childParams.mBottom = -1; + + 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; + } else { + // FIXME uh oh... + } + } + + 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; + } else { + // FIXME uh oh... + } + } + + 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; + } else { + // FIXME uh oh... + } + } + + if (rules[ALIGN_BASELINE] != 0) { + mHasBaselineAlignedChild = true; + } + } + + private View getRelatedView(int[] rules, int relation) { + int id = rules[relation]; + if (id != 0) { + View v = findViewById(id); + if (v == null) { + return null; + } + + // Find the first non-GONE view up the chain + while (v.getVisibility() == View.GONE) { + rules = ((LayoutParams) v.getLayoutParams()).getRules(); + v = v.findViewById(rules[relation]); + if (v == null) { + return null; + } + } + + 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 getRelatedViewBaseline(int[] rules, int relation) { + View v = getRelatedView(rules, relation); + if (v != null) { + return v.getBaseline(); + } + return -1; + } + + private 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 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. + 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 p) { + return new LayoutParams(p); + } + + /** + * Per-child layout information associated with RelativeLayout. + * + * @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 + */ + public static class LayoutParams extends ViewGroup.MarginLayoutParams { + private int[] mRules = new int[VERB_COUNT]; + private int mLeft, mTop, mRight, mBottom; + + /** + * When true, uses the parent as the anchor if the anchor doesn't exist or if + * the anchor's visibility is GONE. + */ + 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[] rules = mRules; + + 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; + } + } + + 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); + } + + @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. This + * method should only be used for constraints that don't refer to another sibling + * (e.g., CENTER_IN_PARENT) or take a boolean value ({@link RelativeLayout#TRUE} + * for true or - for false). To specify a verb that takes a subject, use + * {@link #addRule(int, int)} instead. + * + * @param verb One of the verbs defined by + * {@link android.widget.RelativeLayout RelativeLayout}, such as + * ALIGN_WITH_PARENT_LEFT. + * @see #addRule(int, int) + */ + public void addRule(int verb) { + mRules[verb] = TRUE; + } + + /** + * Adds a layout rule to be interpreted by the RelativeLayout. Use this for + * verbs that take a target, such as a sibling (ALIGN_RIGHT) or a boolean + * value (VISIBLE). + * + * @param verb One of the verbs defined by + * {@link android.widget.RelativeLayout RelativeLayout}, such as + * ALIGN_WITH_PARENT_LEFT. + * @param anchor The id of another view to use as an anchor, + * or a boolean value(represented as {@link RelativeLayout#TRUE}) + * for true or 0 for false). For verbs that don't refer to another sibling + * (for example, ALIGN_WITH_PARENT_BOTTOM) just use -1. + * @see #addRule(int) + */ + public void addRule(int verb, int anchor) { + mRules[verb] = anchor; + } + + /** + * 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. + * + * @return the supported rules + * @see #addRule(int, int) + */ + public int[] getRules() { + return mRules; + } + } +} |