• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import com.android.internal.R;
20 
21 import java.util.ArrayDeque;
22 import java.util.ArrayList;
23 import java.util.Comparator;
24 import java.util.HashMap;
25 import java.util.SortedSet;
26 import java.util.TreeSet;
27 
28 import android.content.Context;
29 import android.content.res.Resources;
30 import android.content.res.TypedArray;
31 import android.graphics.Rect;
32 import android.util.AttributeSet;
33 import android.util.Pool;
34 import android.util.Poolable;
35 import android.util.PoolableManager;
36 import android.util.Pools;
37 import android.util.SparseArray;
38 import android.view.Gravity;
39 import android.view.View;
40 import android.view.ViewDebug;
41 import android.view.ViewGroup;
42 import android.view.accessibility.AccessibilityEvent;
43 import android.view.accessibility.AccessibilityNodeInfo;
44 import android.widget.RemoteViews.RemoteView;
45 
46 import static android.util.Log.d;
47 
48 /**
49  * A Layout where the positions of the children can be described in relation to each other or to the
50  * parent.
51  *
52  * <p>
53  * Note that you cannot have a circular dependency between the size of the RelativeLayout and the
54  * position of its children. For example, you cannot have a RelativeLayout whose height is set to
55  * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT WRAP_CONTENT} and a child set to
56  * {@link #ALIGN_PARENT_BOTTOM}.
57  * </p>
58  *
59  * <p>See the <a href="{@docRoot}guide/topics/ui/layout/relative.html">Relative
60  * Layout</a> guide.</p>
61  *
62  * <p>
63  * Also see {@link android.widget.RelativeLayout.LayoutParams RelativeLayout.LayoutParams} for
64  * layout attributes
65  * </p>
66  *
67  * @attr ref android.R.styleable#RelativeLayout_gravity
68  * @attr ref android.R.styleable#RelativeLayout_ignoreGravity
69  */
70 @RemoteView
71 public class RelativeLayout extends ViewGroup {
72     private static final String LOG_TAG = "RelativeLayout";
73 
74     private static final boolean DEBUG_GRAPH = false;
75 
76     public static final int TRUE = -1;
77 
78     /**
79      * Rule that aligns a child's right edge with another child's left edge.
80      */
81     public static final int LEFT_OF                  = 0;
82     /**
83      * Rule that aligns a child's left edge with another child's right edge.
84      */
85     public static final int RIGHT_OF                 = 1;
86     /**
87      * Rule that aligns a child's bottom edge with another child's top edge.
88      */
89     public static final int ABOVE                    = 2;
90     /**
91      * Rule that aligns a child's top edge with another child's bottom edge.
92      */
93     public static final int BELOW                    = 3;
94 
95     /**
96      * Rule that aligns a child's baseline with another child's baseline.
97      */
98     public static final int ALIGN_BASELINE           = 4;
99     /**
100      * Rule that aligns a child's left edge with another child's left edge.
101      */
102     public static final int ALIGN_LEFT               = 5;
103     /**
104      * Rule that aligns a child's top edge with another child's top edge.
105      */
106     public static final int ALIGN_TOP                = 6;
107     /**
108      * Rule that aligns a child's right edge with another child's right edge.
109      */
110     public static final int ALIGN_RIGHT              = 7;
111     /**
112      * Rule that aligns a child's bottom edge with another child's bottom edge.
113      */
114     public static final int ALIGN_BOTTOM             = 8;
115 
116     /**
117      * Rule that aligns the child's left edge with its RelativeLayout
118      * parent's left edge.
119      */
120     public static final int ALIGN_PARENT_LEFT        = 9;
121     /**
122      * Rule that aligns the child's top edge with its RelativeLayout
123      * parent's top edge.
124      */
125     public static final int ALIGN_PARENT_TOP         = 10;
126     /**
127      * Rule that aligns the child's right edge with its RelativeLayout
128      * parent's right edge.
129      */
130     public static final int ALIGN_PARENT_RIGHT       = 11;
131     /**
132      * Rule that aligns the child's bottom edge with its RelativeLayout
133      * parent's bottom edge.
134      */
135     public static final int ALIGN_PARENT_BOTTOM      = 12;
136 
137     /**
138      * Rule that centers the child with respect to the bounds of its
139      * RelativeLayout parent.
140      */
141     public static final int CENTER_IN_PARENT         = 13;
142     /**
143      * Rule that centers the child horizontally with respect to the
144      * bounds of its RelativeLayout parent.
145      */
146     public static final int CENTER_HORIZONTAL        = 14;
147     /**
148      * Rule that centers the child vertically with respect to the
149      * bounds of its RelativeLayout parent.
150      */
151     public static final int CENTER_VERTICAL          = 15;
152     /**
153      * Rule that aligns a child's end edge with another child's start edge.
154      */
155     public static final int START_OF                 = 16;
156     /**
157      * Rule that aligns a child's start edge with another child's end edge.
158      */
159     public static final int END_OF                   = 17;
160     /**
161      * Rule that aligns a child's start edge with another child's start edge.
162      */
163     public static final int ALIGN_START              = 18;
164     /**
165      * Rule that aligns a child's end edge with another child's end edge.
166      */
167     public static final int ALIGN_END                = 19;
168     /**
169      * Rule that aligns the child's start edge with its RelativeLayout
170      * parent's start edge.
171      */
172     public static final int ALIGN_PARENT_START       = 20;
173     /**
174      * Rule that aligns the child's end edge with its RelativeLayout
175      * parent's end edge.
176      */
177     public static final int ALIGN_PARENT_END         = 21;
178 
179     private static final int VERB_COUNT              = 22;
180 
181 
182     private static final int[] RULES_VERTICAL = {
183             ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM
184     };
185 
186     private static final int[] RULES_HORIZONTAL = {
187             LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END
188     };
189 
190     private View mBaselineView = null;
191     private boolean mHasBaselineAlignedChild;
192 
193     private int mGravity = Gravity.START | Gravity.TOP;
194     private final Rect mContentBounds = new Rect();
195     private final Rect mSelfBounds = new Rect();
196     private int mIgnoreGravity;
197 
198     private SortedSet<View> mTopToBottomLeftToRightSet = null;
199 
200     private boolean mDirtyHierarchy;
201     private View[] mSortedHorizontalChildren = new View[0];
202     private View[] mSortedVerticalChildren = new View[0];
203     private final DependencyGraph mGraph = new DependencyGraph();
204 
RelativeLayout(Context context)205     public RelativeLayout(Context context) {
206         super(context);
207     }
208 
RelativeLayout(Context context, AttributeSet attrs)209     public RelativeLayout(Context context, AttributeSet attrs) {
210         super(context, attrs);
211         initFromAttributes(context, attrs);
212     }
213 
RelativeLayout(Context context, AttributeSet attrs, int defStyle)214     public RelativeLayout(Context context, AttributeSet attrs, int defStyle) {
215         super(context, attrs, defStyle);
216         initFromAttributes(context, attrs);
217     }
218 
initFromAttributes(Context context, AttributeSet attrs)219     private void initFromAttributes(Context context, AttributeSet attrs) {
220         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RelativeLayout);
221         mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, View.NO_ID);
222         mGravity = a.getInt(R.styleable.RelativeLayout_gravity, mGravity);
223         a.recycle();
224     }
225 
226     @Override
shouldDelayChildPressedState()227     public boolean shouldDelayChildPressedState() {
228         return false;
229     }
230 
231     /**
232      * Defines which View is ignored when the gravity is applied. This setting has no
233      * effect if the gravity is <code>Gravity.START | Gravity.TOP</code>.
234      *
235      * @param viewId The id of the View to be ignored by gravity, or 0 if no View
236      *        should be ignored.
237      *
238      * @see #setGravity(int)
239      *
240      * @attr ref android.R.styleable#RelativeLayout_ignoreGravity
241      */
242     @android.view.RemotableViewMethod
setIgnoreGravity(int viewId)243     public void setIgnoreGravity(int viewId) {
244         mIgnoreGravity = viewId;
245     }
246 
247     /**
248      * Describes how the child views are positioned.
249      *
250      * @return the gravity.
251      *
252      * @see #setGravity(int)
253      * @see android.view.Gravity
254      *
255      * @attr ref android.R.styleable#RelativeLayout_gravity
256      */
getGravity()257     public int getGravity() {
258         return mGravity;
259     }
260 
261     /**
262      * Describes how the child views are positioned. Defaults to
263      * <code>Gravity.START | Gravity.TOP</code>.
264      *
265      * <p>Note that since RelativeLayout considers the positioning of each child
266      * relative to one another to be significant, setting gravity will affect
267      * the positioning of all children as a single unit within the parent.
268      * This happens after children have been relatively positioned.</p>
269      *
270      * @param gravity See {@link android.view.Gravity}
271      *
272      * @see #setHorizontalGravity(int)
273      * @see #setVerticalGravity(int)
274      *
275      * @attr ref android.R.styleable#RelativeLayout_gravity
276      */
277     @android.view.RemotableViewMethod
setGravity(int gravity)278     public void setGravity(int gravity) {
279         if (mGravity != gravity) {
280             if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
281                 gravity |= Gravity.START;
282             }
283 
284             if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
285                 gravity |= Gravity.TOP;
286             }
287 
288             mGravity = gravity;
289             requestLayout();
290         }
291     }
292 
293     @android.view.RemotableViewMethod
setHorizontalGravity(int horizontalGravity)294     public void setHorizontalGravity(int horizontalGravity) {
295         final int gravity = horizontalGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
296         if ((mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) {
297             mGravity = (mGravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity;
298             requestLayout();
299         }
300     }
301 
302     @android.view.RemotableViewMethod
setVerticalGravity(int verticalGravity)303     public void setVerticalGravity(int verticalGravity) {
304         final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK;
305         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) {
306             mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity;
307             requestLayout();
308         }
309     }
310 
311     @Override
getBaseline()312     public int getBaseline() {
313         return mBaselineView != null ? mBaselineView.getBaseline() : super.getBaseline();
314     }
315 
316     @Override
requestLayout()317     public void requestLayout() {
318         super.requestLayout();
319         mDirtyHierarchy = true;
320     }
321 
sortChildren()322     private void sortChildren() {
323         int count = getChildCount();
324         if (mSortedVerticalChildren.length != count) mSortedVerticalChildren = new View[count];
325         if (mSortedHorizontalChildren.length != count) mSortedHorizontalChildren = new View[count];
326 
327         final DependencyGraph graph = mGraph;
328         graph.clear();
329 
330         for (int i = 0; i < count; i++) {
331             final View child = getChildAt(i);
332             graph.add(child);
333         }
334 
335         if (DEBUG_GRAPH) {
336             d(LOG_TAG, "=== Sorted vertical children");
337             graph.log(getResources(), RULES_VERTICAL);
338             d(LOG_TAG, "=== Sorted horizontal children");
339             graph.log(getResources(), RULES_HORIZONTAL);
340         }
341 
342         graph.getSortedViews(mSortedVerticalChildren, RULES_VERTICAL);
343         graph.getSortedViews(mSortedHorizontalChildren, RULES_HORIZONTAL);
344 
345         if (DEBUG_GRAPH) {
346             d(LOG_TAG, "=== Ordered list of vertical children");
347             for (View view : mSortedVerticalChildren) {
348                 DependencyGraph.printViewId(getResources(), view);
349             }
350             d(LOG_TAG, "=== Ordered list of horizontal children");
351             for (View view : mSortedHorizontalChildren) {
352                 DependencyGraph.printViewId(getResources(), view);
353             }
354         }
355     }
356 
357     // TODO: we need to find another way to implement RelativeLayout
358     // This implementation cannot handle every case
359     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)360     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
361         if (mDirtyHierarchy) {
362             mDirtyHierarchy = false;
363             sortChildren();
364         }
365 
366         int myWidth = -1;
367         int myHeight = -1;
368 
369         int width = 0;
370         int height = 0;
371 
372         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
373         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
374         final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
375         final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
376 
377         // Record our dimensions if they are known;
378         if (widthMode != MeasureSpec.UNSPECIFIED) {
379             myWidth = widthSize;
380         }
381 
382         if (heightMode != MeasureSpec.UNSPECIFIED) {
383             myHeight = heightSize;
384         }
385 
386         if (widthMode == MeasureSpec.EXACTLY) {
387             width = myWidth;
388         }
389 
390         if (heightMode == MeasureSpec.EXACTLY) {
391             height = myHeight;
392         }
393 
394         mHasBaselineAlignedChild = false;
395 
396         View ignore = null;
397         int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
398         final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;
399         gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
400         final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;
401 
402         int left = Integer.MAX_VALUE;
403         int top = Integer.MAX_VALUE;
404         int right = Integer.MIN_VALUE;
405         int bottom = Integer.MIN_VALUE;
406 
407         boolean offsetHorizontalAxis = false;
408         boolean offsetVerticalAxis = false;
409 
410         if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
411             ignore = findViewById(mIgnoreGravity);
412         }
413 
414         final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
415         final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;
416 
417         View[] views = mSortedHorizontalChildren;
418         int count = views.length;
419 
420         // We need to know our size for doing the correct computation of positioning in RTL mode
421         if (isLayoutRtl() && (myWidth == -1 || isWrapContentWidth)) {
422             int w = getPaddingStart() + getPaddingEnd();
423             final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
424             for (int i = 0; i < count; i++) {
425                 View child = views[i];
426                 if (child.getVisibility() != GONE) {
427                     LayoutParams params = (LayoutParams) child.getLayoutParams();
428                     // Would be similar to a call to measureChildHorizontal(child, params, -1, myHeight)
429                     // but we cannot change for now the behavior of measureChildHorizontal() for
430                     // taking care or a "-1" for "mywidth" so use here our own version of that code.
431                     int childHeightMeasureSpec;
432                     if (params.width == LayoutParams.MATCH_PARENT) {
433                         childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
434                     } else {
435                         childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
436                     }
437                     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
438 
439                     w += child.getMeasuredWidth();
440                     w += params.leftMargin + params.rightMargin;
441                 }
442             }
443             if (myWidth == -1) {
444                 // Easy case: "myWidth" was undefined before so use the width we have just computed
445                 myWidth = w;
446             } else {
447                 // "myWidth" was defined before, so take the min of it and the computed width if it
448                 // is a non null one
449                 if (w > 0) {
450                     myWidth = Math.min(myWidth, w);
451                 }
452             }
453         }
454 
455         for (int i = 0; i < count; i++) {
456             View child = views[i];
457             if (child.getVisibility() != GONE) {
458                 LayoutParams params = (LayoutParams) child.getLayoutParams();
459 
460                 applyHorizontalSizeRules(params, myWidth);
461                 measureChildHorizontal(child, params, myWidth, myHeight);
462                 if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
463                     offsetHorizontalAxis = true;
464                 }
465             }
466         }
467 
468         views = mSortedVerticalChildren;
469         count = views.length;
470 
471         for (int i = 0; i < count; i++) {
472             View child = views[i];
473             if (child.getVisibility() != GONE) {
474                 LayoutParams params = (LayoutParams) child.getLayoutParams();
475 
476                 applyVerticalSizeRules(params, myHeight);
477                 measureChild(child, params, myWidth, myHeight);
478                 if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
479                     offsetVerticalAxis = true;
480                 }
481 
482                 if (isWrapContentWidth) {
483                     width = Math.max(width, params.mRight);
484                 }
485 
486                 if (isWrapContentHeight) {
487                     height = Math.max(height, params.mBottom);
488                 }
489 
490                 if (child != ignore || verticalGravity) {
491                     left = Math.min(left, params.mLeft - params.leftMargin);
492                     top = Math.min(top, params.mTop - params.topMargin);
493                 }
494 
495                 if (child != ignore || horizontalGravity) {
496                     right = Math.max(right, params.mRight + params.rightMargin);
497                     bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
498                 }
499             }
500         }
501 
502         if (mHasBaselineAlignedChild) {
503             for (int i = 0; i < count; i++) {
504                 View child = getChildAt(i);
505                 if (child.getVisibility() != GONE) {
506                     LayoutParams params = (LayoutParams) child.getLayoutParams();
507                     alignBaseline(child, params);
508 
509                     if (child != ignore || verticalGravity) {
510                         left = Math.min(left, params.mLeft - params.leftMargin);
511                         top = Math.min(top, params.mTop - params.topMargin);
512                     }
513 
514                     if (child != ignore || horizontalGravity) {
515                         right = Math.max(right, params.mRight + params.rightMargin);
516                         bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
517                     }
518                 }
519             }
520         }
521 
522         final int layoutDirection = getLayoutDirection();
523 
524         if (isWrapContentWidth) {
525             // Width already has left padding in it since it was calculated by looking at
526             // the right of each child view
527             width += mPaddingRight;
528 
529             if (mLayoutParams.width >= 0) {
530                 width = Math.max(width, mLayoutParams.width);
531             }
532 
533             width = Math.max(width, getSuggestedMinimumWidth());
534             width = resolveSize(width, widthMeasureSpec);
535 
536             if (offsetHorizontalAxis) {
537                 for (int i = 0; i < count; i++) {
538                     View child = getChildAt(i);
539                     if (child.getVisibility() != GONE) {
540                         LayoutParams params = (LayoutParams) child.getLayoutParams();
541                         final int[] rules = params.getRules(layoutDirection);
542                         if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
543                             centerHorizontal(child, params, width);
544                         } else if (rules[ALIGN_PARENT_RIGHT] != 0) {
545                             final int childWidth = child.getMeasuredWidth();
546                             params.mLeft = width - mPaddingRight - childWidth;
547                             params.mRight = params.mLeft + childWidth;
548                         }
549                     }
550                 }
551             }
552         }
553 
554         if (isWrapContentHeight) {
555             // Height already has top padding in it since it was calculated by looking at
556             // the bottom of each child view
557             height += mPaddingBottom;
558 
559             if (mLayoutParams.height >= 0) {
560                 height = Math.max(height, mLayoutParams.height);
561             }
562 
563             height = Math.max(height, getSuggestedMinimumHeight());
564             height = resolveSize(height, heightMeasureSpec);
565 
566             if (offsetVerticalAxis) {
567                 for (int i = 0; i < count; i++) {
568                     View child = getChildAt(i);
569                     if (child.getVisibility() != GONE) {
570                         LayoutParams params = (LayoutParams) child.getLayoutParams();
571                         final int[] rules = params.getRules(layoutDirection);
572                         if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
573                             centerVertical(child, params, height);
574                         } else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
575                             final int childHeight = child.getMeasuredHeight();
576                             params.mTop = height - mPaddingBottom - childHeight;
577                             params.mBottom = params.mTop + childHeight;
578                         }
579                     }
580                 }
581             }
582         }
583 
584         if (horizontalGravity || verticalGravity) {
585             final Rect selfBounds = mSelfBounds;
586             selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
587                     height - mPaddingBottom);
588 
589             final Rect contentBounds = mContentBounds;
590             Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
591                     layoutDirection);
592 
593             final int horizontalOffset = contentBounds.left - left;
594             final int verticalOffset = contentBounds.top - top;
595             if (horizontalOffset != 0 || verticalOffset != 0) {
596                 for (int i = 0; i < count; i++) {
597                     View child = getChildAt(i);
598                     if (child.getVisibility() != GONE && child != ignore) {
599                         LayoutParams params = (LayoutParams) child.getLayoutParams();
600                         if (horizontalGravity) {
601                             params.mLeft += horizontalOffset;
602                             params.mRight += horizontalOffset;
603                         }
604                         if (verticalGravity) {
605                             params.mTop += verticalOffset;
606                             params.mBottom += verticalOffset;
607                         }
608                     }
609                 }
610             }
611         }
612 
613         setMeasuredDimension(width, height);
614     }
615 
alignBaseline(View child, LayoutParams params)616     private void alignBaseline(View child, LayoutParams params) {
617         final int layoutDirection = getLayoutDirection();
618         int[] rules = params.getRules(layoutDirection);
619         int anchorBaseline = getRelatedViewBaseline(rules, ALIGN_BASELINE);
620 
621         if (anchorBaseline != -1) {
622             LayoutParams anchorParams = getRelatedViewParams(rules, ALIGN_BASELINE);
623             if (anchorParams != null) {
624                 int offset = anchorParams.mTop + anchorBaseline;
625                 int baseline = child.getBaseline();
626                 if (baseline != -1) {
627                     offset -= baseline;
628                 }
629                 int height = params.mBottom - params.mTop;
630                 params.mTop = offset;
631                 params.mBottom = params.mTop + height;
632             }
633         }
634 
635         if (mBaselineView == null) {
636             mBaselineView = child;
637         } else {
638             LayoutParams lp = (LayoutParams) mBaselineView.getLayoutParams();
639             if (params.mTop < lp.mTop || (params.mTop == lp.mTop && params.mLeft < lp.mLeft)) {
640                 mBaselineView = child;
641             }
642         }
643     }
644 
645     /**
646      * Measure a child. The child should have left, top, right and bottom information
647      * stored in its LayoutParams. If any of these values is -1 it means that the view
648      * can extend up to the corresponding edge.
649      *
650      * @param child Child to measure
651      * @param params LayoutParams associated with child
652      * @param myWidth Width of the the RelativeLayout
653      * @param myHeight Height of the RelativeLayout
654      */
measureChild(View child, LayoutParams params, int myWidth, int myHeight)655     private void measureChild(View child, LayoutParams params, int myWidth, int myHeight) {
656         int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft,
657                 params.mRight, params.width,
658                 params.leftMargin, params.rightMargin,
659                 mPaddingLeft, mPaddingRight,
660                 myWidth);
661         int childHeightMeasureSpec = getChildMeasureSpec(params.mTop,
662                 params.mBottom, params.height,
663                 params.topMargin, params.bottomMargin,
664                 mPaddingTop, mPaddingBottom,
665                 myHeight);
666         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
667     }
668 
measureChildHorizontal(View child, LayoutParams params, int myWidth, int myHeight)669     private void measureChildHorizontal(View child, LayoutParams params, int myWidth, int myHeight) {
670         int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft,
671                 params.mRight, params.width,
672                 params.leftMargin, params.rightMargin,
673                 mPaddingLeft, mPaddingRight,
674                 myWidth);
675         int childHeightMeasureSpec;
676         if (params.width == LayoutParams.MATCH_PARENT) {
677             childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
678         } else {
679             childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
680         }
681         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
682     }
683 
684     /**
685      * Get a measure spec that accounts for all of the constraints on this view.
686      * This includes size constraints imposed by the RelativeLayout as well as
687      * the View's desired dimension.
688      *
689      * @param childStart The left or top field of the child's layout params
690      * @param childEnd The right or bottom field of the child's layout params
691      * @param childSize The child's desired size (the width or height field of
692      *        the child's layout params)
693      * @param startMargin The left or top margin
694      * @param endMargin The right or bottom margin
695      * @param startPadding mPaddingLeft or mPaddingTop
696      * @param endPadding mPaddingRight or mPaddingBottom
697      * @param mySize The width or height of this view (the RelativeLayout)
698      * @return MeasureSpec for the child
699      */
getChildMeasureSpec(int childStart, int childEnd, int childSize, int startMargin, int endMargin, int startPadding, int endPadding, int mySize)700     private int getChildMeasureSpec(int childStart, int childEnd,
701             int childSize, int startMargin, int endMargin, int startPadding,
702             int endPadding, int mySize) {
703         int childSpecMode = 0;
704         int childSpecSize = 0;
705 
706         // Figure out start and end bounds.
707         int tempStart = childStart;
708         int tempEnd = childEnd;
709 
710         // If the view did not express a layout constraint for an edge, use
711         // view's margins and our padding
712         if (tempStart < 0) {
713             tempStart = startPadding + startMargin;
714         }
715         if (tempEnd < 0) {
716             tempEnd = mySize - endPadding - endMargin;
717         }
718 
719         // Figure out maximum size available to this view
720         int maxAvailable = tempEnd - tempStart;
721 
722         if (childStart >= 0 && childEnd >= 0) {
723             // Constraints fixed both edges, so child must be an exact size
724             childSpecMode = MeasureSpec.EXACTLY;
725             childSpecSize = maxAvailable;
726         } else {
727             if (childSize >= 0) {
728                 // Child wanted an exact size. Give as much as possible
729                 childSpecMode = MeasureSpec.EXACTLY;
730 
731                 if (maxAvailable >= 0) {
732                     // We have a maxmum size in this dimension.
733                     childSpecSize = Math.min(maxAvailable, childSize);
734                 } else {
735                     // We can grow in this dimension.
736                     childSpecSize = childSize;
737                 }
738             } else if (childSize == LayoutParams.MATCH_PARENT) {
739                 // Child wanted to be as big as possible. Give all available
740                 // space
741                 childSpecMode = MeasureSpec.EXACTLY;
742                 childSpecSize = maxAvailable;
743             } else if (childSize == LayoutParams.WRAP_CONTENT) {
744                 // Child wants to wrap content. Use AT_MOST
745                 // to communicate available space if we know
746                 // our max size
747                 if (maxAvailable >= 0) {
748                     // We have a maximum size in this dimension.
749                     childSpecMode = MeasureSpec.AT_MOST;
750                     childSpecSize = maxAvailable;
751                 } else {
752                     // We can grow in this dimension. Child can be as big as it
753                     // wants
754                     childSpecMode = MeasureSpec.UNSPECIFIED;
755                     childSpecSize = 0;
756                 }
757             }
758         }
759 
760         return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
761     }
762 
positionChildHorizontal(View child, LayoutParams params, int myWidth, boolean wrapContent)763     private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth,
764             boolean wrapContent) {
765 
766         final int layoutDirection = getLayoutDirection();
767         int[] rules = params.getRules(layoutDirection);
768 
769         if (params.mLeft < 0 && params.mRight >= 0) {
770             // Right is fixed, but left varies
771             params.mLeft = params.mRight - child.getMeasuredWidth();
772         } else if (params.mLeft >= 0 && params.mRight < 0) {
773             // Left is fixed, but right varies
774             params.mRight = params.mLeft + child.getMeasuredWidth();
775         } else if (params.mLeft < 0 && params.mRight < 0) {
776             // Both left and right vary
777             if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
778                 if (!wrapContent) {
779                     centerHorizontal(child, params, myWidth);
780                 } else {
781                     params.mLeft = mPaddingLeft + params.leftMargin;
782                     params.mRight = params.mLeft + child.getMeasuredWidth();
783                 }
784                 return true;
785             } else {
786                 // This is the default case. For RTL we start from the right and for LTR we start
787                 // from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL.
788                 if (isLayoutRtl()) {
789                     params.mRight = myWidth - mPaddingRight- params.rightMargin;
790                     params.mLeft = params.mRight - child.getMeasuredWidth();
791                 } else {
792                     params.mLeft = mPaddingLeft + params.leftMargin;
793                     params.mRight = params.mLeft + child.getMeasuredWidth();
794                 }
795             }
796         }
797         return rules[ALIGN_PARENT_END] != 0;
798     }
799 
positionChildVertical(View child, LayoutParams params, int myHeight, boolean wrapContent)800     private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
801             boolean wrapContent) {
802 
803         int[] rules = params.getRules();
804 
805         if (params.mTop < 0 && params.mBottom >= 0) {
806             // Bottom is fixed, but top varies
807             params.mTop = params.mBottom - child.getMeasuredHeight();
808         } else if (params.mTop >= 0 && params.mBottom < 0) {
809             // Top is fixed, but bottom varies
810             params.mBottom = params.mTop + child.getMeasuredHeight();
811         } else if (params.mTop < 0 && params.mBottom < 0) {
812             // Both top and bottom vary
813             if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
814                 if (!wrapContent) {
815                     centerVertical(child, params, myHeight);
816                 } else {
817                     params.mTop = mPaddingTop + params.topMargin;
818                     params.mBottom = params.mTop + child.getMeasuredHeight();
819                 }
820                 return true;
821             } else {
822                 params.mTop = mPaddingTop + params.topMargin;
823                 params.mBottom = params.mTop + child.getMeasuredHeight();
824             }
825         }
826         return rules[ALIGN_PARENT_BOTTOM] != 0;
827     }
828 
applyHorizontalSizeRules(LayoutParams childParams, int myWidth)829     private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) {
830         final int layoutDirection = getLayoutDirection();
831         int[] rules = childParams.getRules(layoutDirection);
832         RelativeLayout.LayoutParams anchorParams;
833 
834         // -1 indicated a "soft requirement" in that direction. For example:
835         // left=10, right=-1 means the view must start at 10, but can go as far as it wants to the right
836         // left =-1, right=10 means the view must end at 10, but can go as far as it wants to the left
837         // left=10, right=20 means the left and right ends are both fixed
838         childParams.mLeft = -1;
839         childParams.mRight = -1;
840 
841         anchorParams = getRelatedViewParams(rules, LEFT_OF);
842         if (anchorParams != null) {
843             childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
844                     childParams.rightMargin);
845         } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) {
846             if (myWidth >= 0) {
847                 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
848             } else {
849                 // FIXME uh oh...
850             }
851         }
852 
853         anchorParams = getRelatedViewParams(rules, RIGHT_OF);
854         if (anchorParams != null) {
855             childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin +
856                     childParams.leftMargin);
857         } else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) {
858             childParams.mLeft = mPaddingLeft + childParams.leftMargin;
859         }
860 
861         anchorParams = getRelatedViewParams(rules, ALIGN_LEFT);
862         if (anchorParams != null) {
863             childParams.mLeft = anchorParams.mLeft + childParams.leftMargin;
864         } else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) {
865             childParams.mLeft = mPaddingLeft + childParams.leftMargin;
866         }
867 
868         anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT);
869         if (anchorParams != null) {
870             childParams.mRight = anchorParams.mRight - childParams.rightMargin;
871         } else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) {
872             if (myWidth >= 0) {
873                 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
874             } else {
875                 // FIXME uh oh...
876             }
877         }
878 
879         if (0 != rules[ALIGN_PARENT_LEFT]) {
880             childParams.mLeft = mPaddingLeft + childParams.leftMargin;
881         }
882 
883         if (0 != rules[ALIGN_PARENT_RIGHT]) {
884             if (myWidth >= 0) {
885                 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
886             } else {
887                 // FIXME uh oh...
888             }
889         }
890     }
891 
applyVerticalSizeRules(LayoutParams childParams, int myHeight)892     private void applyVerticalSizeRules(LayoutParams childParams, int myHeight) {
893         int[] rules = childParams.getRules();
894         RelativeLayout.LayoutParams anchorParams;
895 
896         childParams.mTop = -1;
897         childParams.mBottom = -1;
898 
899         anchorParams = getRelatedViewParams(rules, ABOVE);
900         if (anchorParams != null) {
901             childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin +
902                     childParams.bottomMargin);
903         } else if (childParams.alignWithParent && rules[ABOVE] != 0) {
904             if (myHeight >= 0) {
905                 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
906             } else {
907                 // FIXME uh oh...
908             }
909         }
910 
911         anchorParams = getRelatedViewParams(rules, BELOW);
912         if (anchorParams != null) {
913             childParams.mTop = anchorParams.mBottom + (anchorParams.bottomMargin +
914                     childParams.topMargin);
915         } else if (childParams.alignWithParent && rules[BELOW] != 0) {
916             childParams.mTop = mPaddingTop + childParams.topMargin;
917         }
918 
919         anchorParams = getRelatedViewParams(rules, ALIGN_TOP);
920         if (anchorParams != null) {
921             childParams.mTop = anchorParams.mTop + childParams.topMargin;
922         } else if (childParams.alignWithParent && rules[ALIGN_TOP] != 0) {
923             childParams.mTop = mPaddingTop + childParams.topMargin;
924         }
925 
926         anchorParams = getRelatedViewParams(rules, ALIGN_BOTTOM);
927         if (anchorParams != null) {
928             childParams.mBottom = anchorParams.mBottom - childParams.bottomMargin;
929         } else if (childParams.alignWithParent && rules[ALIGN_BOTTOM] != 0) {
930             if (myHeight >= 0) {
931                 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
932             } else {
933                 // FIXME uh oh...
934             }
935         }
936 
937         if (0 != rules[ALIGN_PARENT_TOP]) {
938             childParams.mTop = mPaddingTop + childParams.topMargin;
939         }
940 
941         if (0 != rules[ALIGN_PARENT_BOTTOM]) {
942             if (myHeight >= 0) {
943                 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
944             } else {
945                 // FIXME uh oh...
946             }
947         }
948 
949         if (rules[ALIGN_BASELINE] != 0) {
950             mHasBaselineAlignedChild = true;
951         }
952     }
953 
getRelatedView(int[] rules, int relation)954     private View getRelatedView(int[] rules, int relation) {
955         int id = rules[relation];
956         if (id != 0) {
957             DependencyGraph.Node node = mGraph.mKeyNodes.get(id);
958             if (node == null) return null;
959             View v = node.view;
960 
961             // Find the first non-GONE view up the chain
962             while (v.getVisibility() == View.GONE) {
963                 rules = ((LayoutParams) v.getLayoutParams()).getRules(v.getLayoutDirection());
964                 node = mGraph.mKeyNodes.get((rules[relation]));
965                 if (node == null) return null;
966                 v = node.view;
967             }
968 
969             return v;
970         }
971 
972         return null;
973     }
974 
getRelatedViewParams(int[] rules, int relation)975     private LayoutParams getRelatedViewParams(int[] rules, int relation) {
976         View v = getRelatedView(rules, relation);
977         if (v != null) {
978             ViewGroup.LayoutParams params = v.getLayoutParams();
979             if (params instanceof LayoutParams) {
980                 return (LayoutParams) v.getLayoutParams();
981             }
982         }
983         return null;
984     }
985 
getRelatedViewBaseline(int[] rules, int relation)986     private int getRelatedViewBaseline(int[] rules, int relation) {
987         View v = getRelatedView(rules, relation);
988         if (v != null) {
989             return v.getBaseline();
990         }
991         return -1;
992     }
993 
centerHorizontal(View child, LayoutParams params, int myWidth)994     private void centerHorizontal(View child, LayoutParams params, int myWidth) {
995         int childWidth = child.getMeasuredWidth();
996         int left = (myWidth - childWidth) / 2;
997 
998         params.mLeft = left;
999         params.mRight = left + childWidth;
1000     }
1001 
centerVertical(View child, LayoutParams params, int myHeight)1002     private void centerVertical(View child, LayoutParams params, int myHeight) {
1003         int childHeight = child.getMeasuredHeight();
1004         int top = (myHeight - childHeight) / 2;
1005 
1006         params.mTop = top;
1007         params.mBottom = top + childHeight;
1008     }
1009 
1010     @Override
onLayout(boolean changed, int l, int t, int r, int b)1011     protected void onLayout(boolean changed, int l, int t, int r, int b) {
1012         //  The layout has actually already been performed and the positions
1013         //  cached.  Apply the cached values to the children.
1014         final int count = getChildCount();
1015 
1016         for (int i = 0; i < count; i++) {
1017             View child = getChildAt(i);
1018             if (child.getVisibility() != GONE) {
1019                 RelativeLayout.LayoutParams st =
1020                         (RelativeLayout.LayoutParams) child.getLayoutParams();
1021                 child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
1022             }
1023         }
1024     }
1025 
1026     @Override
generateLayoutParams(AttributeSet attrs)1027     public LayoutParams generateLayoutParams(AttributeSet attrs) {
1028         return new RelativeLayout.LayoutParams(getContext(), attrs);
1029     }
1030 
1031     /**
1032      * Returns a set of layout parameters with a width of
1033      * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT},
1034      * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
1035      */
1036     @Override
generateDefaultLayoutParams()1037     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1038         return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
1039     }
1040 
1041     // Override to allow type-checking of LayoutParams.
1042     @Override
checkLayoutParams(ViewGroup.LayoutParams p)1043     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1044         return p instanceof RelativeLayout.LayoutParams;
1045     }
1046 
1047     @Override
generateLayoutParams(ViewGroup.LayoutParams p)1048     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1049         return new LayoutParams(p);
1050     }
1051 
1052     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)1053     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1054         if (mTopToBottomLeftToRightSet == null) {
1055             mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator());
1056         }
1057 
1058         // sort children top-to-bottom and left-to-right
1059         for (int i = 0, count = getChildCount(); i < count; i++) {
1060             mTopToBottomLeftToRightSet.add(getChildAt(i));
1061         }
1062 
1063         for (View view : mTopToBottomLeftToRightSet) {
1064             if (view.getVisibility() == View.VISIBLE
1065                     && view.dispatchPopulateAccessibilityEvent(event)) {
1066                 mTopToBottomLeftToRightSet.clear();
1067                 return true;
1068             }
1069         }
1070 
1071         mTopToBottomLeftToRightSet.clear();
1072         return false;
1073     }
1074 
1075     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)1076     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1077         super.onInitializeAccessibilityEvent(event);
1078         event.setClassName(RelativeLayout.class.getName());
1079     }
1080 
1081     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)1082     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1083         super.onInitializeAccessibilityNodeInfo(info);
1084         info.setClassName(RelativeLayout.class.getName());
1085     }
1086 
1087     /**
1088      * Compares two views in left-to-right and top-to-bottom fashion.
1089      */
1090      private class TopToBottomLeftToRightComparator implements Comparator<View> {
compare(View first, View second)1091         public int compare(View first, View second) {
1092             // top - bottom
1093             int topDifference = first.getTop() - second.getTop();
1094             if (topDifference != 0) {
1095                 return topDifference;
1096             }
1097             // left - right
1098             int leftDifference = first.getLeft() - second.getLeft();
1099             if (leftDifference != 0) {
1100                 return leftDifference;
1101             }
1102             // break tie by height
1103             int heightDiference = first.getHeight() - second.getHeight();
1104             if (heightDiference != 0) {
1105                 return heightDiference;
1106             }
1107             // break tie by width
1108             int widthDiference = first.getWidth() - second.getWidth();
1109             if (widthDiference != 0) {
1110                 return widthDiference;
1111             }
1112             return 0;
1113         }
1114     }
1115 
1116     /**
1117      * Per-child layout information associated with RelativeLayout.
1118      *
1119      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignWithParentIfMissing
1120      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toLeftOf
1121      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toRightOf
1122      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_above
1123      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_below
1124      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBaseline
1125      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignLeft
1126      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignTop
1127      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignRight
1128      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBottom
1129      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentLeft
1130      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentTop
1131      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentRight
1132      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentBottom
1133      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent
1134      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal
1135      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical
1136      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toStartOf
1137      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toEndOf
1138      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignStart
1139      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignEnd
1140      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentStart
1141      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentEnd
1142      */
1143     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1144         @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = {
1145             @ViewDebug.IntToString(from = ABOVE,               to = "above"),
1146             @ViewDebug.IntToString(from = ALIGN_BASELINE,      to = "alignBaseline"),
1147             @ViewDebug.IntToString(from = ALIGN_BOTTOM,        to = "alignBottom"),
1148             @ViewDebug.IntToString(from = ALIGN_LEFT,          to = "alignLeft"),
1149             @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"),
1150             @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT,   to = "alignParentLeft"),
1151             @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT,  to = "alignParentRight"),
1152             @ViewDebug.IntToString(from = ALIGN_PARENT_TOP,    to = "alignParentTop"),
1153             @ViewDebug.IntToString(from = ALIGN_RIGHT,         to = "alignRight"),
1154             @ViewDebug.IntToString(from = ALIGN_TOP,           to = "alignTop"),
1155             @ViewDebug.IntToString(from = BELOW,               to = "below"),
1156             @ViewDebug.IntToString(from = CENTER_HORIZONTAL,   to = "centerHorizontal"),
1157             @ViewDebug.IntToString(from = CENTER_IN_PARENT,    to = "center"),
1158             @ViewDebug.IntToString(from = CENTER_VERTICAL,     to = "centerVertical"),
1159             @ViewDebug.IntToString(from = LEFT_OF,             to = "leftOf"),
1160             @ViewDebug.IntToString(from = RIGHT_OF,            to = "rightOf"),
1161             @ViewDebug.IntToString(from = ALIGN_START,         to = "alignStart"),
1162             @ViewDebug.IntToString(from = ALIGN_END,           to = "alignEnd"),
1163             @ViewDebug.IntToString(from = ALIGN_PARENT_START,  to = "alignParentStart"),
1164             @ViewDebug.IntToString(from = ALIGN_PARENT_END,    to = "alignParentEnd"),
1165             @ViewDebug.IntToString(from = START_OF,            to = "startOf"),
1166             @ViewDebug.IntToString(from = END_OF,              to = "endOf")
1167         }, mapping = {
1168             @ViewDebug.IntToString(from = TRUE, to = "true"),
1169             @ViewDebug.IntToString(from = 0,    to = "false/NO_ID")
1170         })
1171 
1172         private int[] mRules = new int[VERB_COUNT];
1173         private int[] mInitialRules = new int[VERB_COUNT];
1174 
1175         private int mLeft, mTop, mRight, mBottom;
1176 
1177         private int mStart = DEFAULT_RELATIVE;
1178         private int mEnd = DEFAULT_RELATIVE;
1179 
1180         private boolean mRulesChanged = false;
1181 
1182         /**
1183          * When true, uses the parent as the anchor if the anchor doesn't exist or if
1184          * the anchor's visibility is GONE.
1185          */
1186         @ViewDebug.ExportedProperty(category = "layout")
1187         public boolean alignWithParent;
1188 
LayoutParams(Context c, AttributeSet attrs)1189         public LayoutParams(Context c, AttributeSet attrs) {
1190             super(c, attrs);
1191 
1192             TypedArray a = c.obtainStyledAttributes(attrs,
1193                     com.android.internal.R.styleable.RelativeLayout_Layout);
1194 
1195             final int[] rules = mRules;
1196             final int[] initialRules = mInitialRules;
1197 
1198             final int N = a.getIndexCount();
1199             for (int i = 0; i < N; i++) {
1200                 int attr = a.getIndex(i);
1201                 switch (attr) {
1202                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:
1203                         alignWithParent = a.getBoolean(attr, false);
1204                         break;
1205                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
1206                         rules[LEFT_OF] = a.getResourceId(attr, 0);
1207                         break;
1208                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:
1209                         rules[RIGHT_OF] = a.getResourceId(attr, 0);
1210                         break;
1211                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:
1212                         rules[ABOVE] = a.getResourceId(attr, 0);
1213                         break;
1214                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below:
1215                         rules[BELOW] = a.getResourceId(attr, 0);
1216                         break;
1217                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline:
1218                         rules[ALIGN_BASELINE] = a.getResourceId(attr, 0);
1219                         break;
1220                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft:
1221                         rules[ALIGN_LEFT] = a.getResourceId(attr, 0);
1222                         break;
1223                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop:
1224                         rules[ALIGN_TOP] = a.getResourceId(attr, 0);
1225                         break;
1226                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight:
1227                         rules[ALIGN_RIGHT] = a.getResourceId(attr, 0);
1228                         break;
1229                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom:
1230                         rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0);
1231                         break;
1232                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft:
1233                         rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0;
1234                         break;
1235                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop:
1236                         rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0;
1237                         break;
1238                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight:
1239                         rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0;
1240                         break;
1241                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom:
1242                         rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0;
1243                         break;
1244                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent:
1245                         rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;
1246                         break;
1247                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal:
1248                         rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0;
1249                         break;
1250                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical:
1251                         rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;
1252                        break;
1253                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf:
1254                         rules[START_OF] = a.getResourceId(attr, 0);
1255                         break;
1256                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf:
1257                         rules[END_OF] = a.getResourceId(attr, 0);
1258                         break;
1259                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart:
1260                         rules[ALIGN_START] = a.getResourceId(attr, 0);
1261                         break;
1262                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd:
1263                         rules[ALIGN_END] = a.getResourceId(attr, 0);
1264                         break;
1265                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart:
1266                         rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0;
1267                         break;
1268                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd:
1269                         rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0;
1270                         break;
1271                 }
1272             }
1273 
1274             for (int n = LEFT_OF; n < VERB_COUNT; n++) {
1275                 initialRules[n] = rules[n];
1276             }
1277 
1278             a.recycle();
1279         }
1280 
LayoutParams(int w, int h)1281         public LayoutParams(int w, int h) {
1282             super(w, h);
1283         }
1284 
1285         /**
1286          * {@inheritDoc}
1287          */
LayoutParams(ViewGroup.LayoutParams source)1288         public LayoutParams(ViewGroup.LayoutParams source) {
1289             super(source);
1290         }
1291 
1292         /**
1293          * {@inheritDoc}
1294          */
LayoutParams(ViewGroup.MarginLayoutParams source)1295         public LayoutParams(ViewGroup.MarginLayoutParams source) {
1296             super(source);
1297         }
1298 
1299         @Override
debug(String output)1300         public String debug(String output) {
1301             return output + "ViewGroup.LayoutParams={ width=" + sizeToString(width) +
1302                     ", height=" + sizeToString(height) + " }";
1303         }
1304 
1305         /**
1306          * Adds a layout rule to be interpreted by the RelativeLayout. This
1307          * method should only be used for constraints that don't refer to another sibling
1308          * (e.g., CENTER_IN_PARENT) or take a boolean value ({@link RelativeLayout#TRUE}
1309          * for true or 0 for false). To specify a verb that takes a subject, use
1310          * {@link #addRule(int, int)} instead.
1311          *
1312          * @param verb One of the verbs defined by
1313          *        {@link android.widget.RelativeLayout RelativeLayout}, such as
1314          *        ALIGN_WITH_PARENT_LEFT.
1315          * @see #addRule(int, int)
1316          */
addRule(int verb)1317         public void addRule(int verb) {
1318             mRules[verb] = TRUE;
1319             mInitialRules[verb] = TRUE;
1320             mRulesChanged = true;
1321         }
1322 
1323         /**
1324          * Adds a layout rule to be interpreted by the RelativeLayout. Use this for
1325          * verbs that take a target, such as a sibling (ALIGN_RIGHT) or a boolean
1326          * value (VISIBLE).
1327          *
1328          * @param verb One of the verbs defined by
1329          *        {@link android.widget.RelativeLayout RelativeLayout}, such as
1330          *         ALIGN_WITH_PARENT_LEFT.
1331          * @param anchor The id of another view to use as an anchor,
1332          *        or a boolean value(represented as {@link RelativeLayout#TRUE})
1333          *        for true or 0 for false).  For verbs that don't refer to another sibling
1334          *        (for example, ALIGN_WITH_PARENT_BOTTOM) just use -1.
1335          * @see #addRule(int)
1336          */
addRule(int verb, int anchor)1337         public void addRule(int verb, int anchor) {
1338             mRules[verb] = anchor;
1339             mInitialRules[verb] = anchor;
1340             mRulesChanged = true;
1341         }
1342 
1343         /**
1344          * Removes a layout rule to be interpreted by the RelativeLayout.
1345          *
1346          * @param verb One of the verbs defined by
1347          *        {@link android.widget.RelativeLayout RelativeLayout}, such as
1348          *         ALIGN_WITH_PARENT_LEFT.
1349          * @see #addRule(int)
1350          * @see #addRule(int, int)
1351          */
removeRule(int verb)1352         public void removeRule(int verb) {
1353             mRules[verb] = 0;
1354             mInitialRules[verb] = 0;
1355             mRulesChanged = true;
1356         }
1357 
hasRelativeRules()1358         private boolean hasRelativeRules() {
1359             return (mInitialRules[START_OF] != 0 || mInitialRules[END_OF] != 0 ||
1360                     mInitialRules[ALIGN_START] != 0 || mInitialRules[ALIGN_END] != 0 ||
1361                     mInitialRules[ALIGN_PARENT_START] != 0 || mInitialRules[ALIGN_PARENT_END] != 0);
1362         }
1363 
resolveRules(int layoutDirection)1364         private void resolveRules(int layoutDirection) {
1365             final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL);
1366             // Reset to initial state
1367             for (int n = LEFT_OF; n < VERB_COUNT; n++) {
1368                 mRules[n] = mInitialRules[n];
1369             }
1370             // Apply rules depending on direction
1371             if (mRules[ALIGN_START] != 0) {
1372                 mRules[isLayoutRtl ? ALIGN_RIGHT : ALIGN_LEFT] = mRules[ALIGN_START];
1373             }
1374             if (mRules[ALIGN_END] != 0) {
1375                 mRules[isLayoutRtl ? ALIGN_LEFT : ALIGN_RIGHT] = mRules[ALIGN_END];
1376             }
1377             if (mRules[START_OF] != 0) {
1378                 mRules[isLayoutRtl ? RIGHT_OF : LEFT_OF] = mRules[START_OF];
1379             }
1380             if (mRules[END_OF] != 0) {
1381                 mRules[isLayoutRtl ? LEFT_OF : RIGHT_OF] = mRules[END_OF];
1382             }
1383             if (mRules[ALIGN_PARENT_START] != 0) {
1384                 mRules[isLayoutRtl ? ALIGN_PARENT_RIGHT : ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START];
1385             }
1386             if (mRules[ALIGN_PARENT_END] != 0) {
1387                 mRules[isLayoutRtl ? ALIGN_PARENT_LEFT : ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END];
1388             }
1389             mRulesChanged = false;
1390         }
1391 
1392         /**
1393          * Retrieves a complete list of all supported rules, where the index is the rule
1394          * verb, and the element value is the value specified, or "false" if it was never
1395          * set. If there are relative rules defined (*_START / *_END), they will be resolved
1396          * depending on the layout direction.
1397          *
1398          * @param layoutDirection the direction of the layout.
1399          *                        Should be either {@link View#LAYOUT_DIRECTION_LTR}
1400          *                        or {@link View#LAYOUT_DIRECTION_RTL}
1401          * @return the supported rules
1402          * @see #addRule(int, int)
1403          *
1404          * @hide
1405          */
getRules(int layoutDirection)1406         public int[] getRules(int layoutDirection) {
1407             if (hasRelativeRules() &&
1408                     (mRulesChanged || layoutDirection != getLayoutDirection())) {
1409                 resolveRules(layoutDirection);
1410                 if (layoutDirection != getLayoutDirection()) {
1411                     setLayoutDirection(layoutDirection);
1412                 }
1413             }
1414             return mRules;
1415         }
1416 
1417         /**
1418          * Retrieves a complete list of all supported rules, where the index is the rule
1419          * verb, and the element value is the value specified, or "false" if it was never
1420          * set. There will be no resolution of relative rules done.
1421          *
1422          * @return the supported rules
1423          * @see #addRule(int, int)
1424          */
getRules()1425         public int[] getRules() {
1426             return mRules;
1427         }
1428 
1429         @Override
resolveLayoutDirection(int layoutDirection)1430         public void resolveLayoutDirection(int layoutDirection) {
1431             final boolean isLayoutRtl = isLayoutRtl();
1432             if (isLayoutRtl) {
1433                 if (mStart != DEFAULT_RELATIVE) mRight = mStart;
1434                 if (mEnd != DEFAULT_RELATIVE) mLeft = mEnd;
1435             } else {
1436                 if (mStart != DEFAULT_RELATIVE) mLeft = mStart;
1437                 if (mEnd != DEFAULT_RELATIVE) mRight = mEnd;
1438             }
1439 
1440             if (hasRelativeRules() && layoutDirection != getLayoutDirection()) {
1441                 resolveRules(layoutDirection);
1442             }
1443             // This will set the layout direction
1444             super.resolveLayoutDirection(layoutDirection);
1445         }
1446     }
1447 
1448     private static class DependencyGraph {
1449         /**
1450          * List of all views in the graph.
1451          */
1452         private ArrayList<Node> mNodes = new ArrayList<Node>();
1453 
1454         /**
1455          * List of nodes in the graph. Each node is identified by its
1456          * view id (see View#getId()).
1457          */
1458         private SparseArray<Node> mKeyNodes = new SparseArray<Node>();
1459 
1460         /**
1461          * Temporary data structure used to build the list of roots
1462          * for this graph.
1463          */
1464         private ArrayDeque<Node> mRoots = new ArrayDeque<Node>();
1465 
1466         /**
1467          * Clears the graph.
1468          */
clear()1469         void clear() {
1470             final ArrayList<Node> nodes = mNodes;
1471             final int count = nodes.size();
1472 
1473             for (int i = 0; i < count; i++) {
1474                 nodes.get(i).release();
1475             }
1476             nodes.clear();
1477 
1478             mKeyNodes.clear();
1479             mRoots.clear();
1480         }
1481 
1482         /**
1483          * Adds a view to the graph.
1484          *
1485          * @param view The view to be added as a node to the graph.
1486          */
add(View view)1487         void add(View view) {
1488             final int id = view.getId();
1489             final Node node = Node.acquire(view);
1490 
1491             if (id != View.NO_ID) {
1492                 mKeyNodes.put(id, node);
1493             }
1494 
1495             mNodes.add(node);
1496         }
1497 
1498         /**
1499          * Builds a sorted list of views. The sorting order depends on the dependencies
1500          * between the view. For instance, if view C needs view A to be processed first
1501          * and view A needs view B to be processed first, the dependency graph
1502          * is: B -> A -> C. The sorted array will contain views B, A and C in this order.
1503          *
1504          * @param sorted The sorted list of views. The length of this array must
1505          *        be equal to getChildCount().
1506          * @param rules The list of rules to take into account.
1507          */
getSortedViews(View[] sorted, int... rules)1508         void getSortedViews(View[] sorted, int... rules) {
1509             final ArrayDeque<Node> roots = findRoots(rules);
1510             int index = 0;
1511 
1512             Node node;
1513             while ((node = roots.pollLast()) != null) {
1514                 final View view = node.view;
1515                 final int key = view.getId();
1516 
1517                 sorted[index++] = view;
1518 
1519                 final HashMap<Node, DependencyGraph> dependents = node.dependents;
1520                 for (Node dependent : dependents.keySet()) {
1521                     final SparseArray<Node> dependencies = dependent.dependencies;
1522 
1523                     dependencies.remove(key);
1524                     if (dependencies.size() == 0) {
1525                         roots.add(dependent);
1526                     }
1527                 }
1528             }
1529 
1530             if (index < sorted.length) {
1531                 throw new IllegalStateException("Circular dependencies cannot exist"
1532                         + " in RelativeLayout");
1533             }
1534         }
1535 
1536         /**
1537          * Finds the roots of the graph. A root is a node with no dependency and
1538          * with [0..n] dependents.
1539          *
1540          * @param rulesFilter The list of rules to consider when building the
1541          *        dependencies
1542          *
1543          * @return A list of node, each being a root of the graph
1544          */
findRoots(int[] rulesFilter)1545         private ArrayDeque<Node> findRoots(int[] rulesFilter) {
1546             final SparseArray<Node> keyNodes = mKeyNodes;
1547             final ArrayList<Node> nodes = mNodes;
1548             final int count = nodes.size();
1549 
1550             // Find roots can be invoked several times, so make sure to clear
1551             // all dependents and dependencies before running the algorithm
1552             for (int i = 0; i < count; i++) {
1553                 final Node node = nodes.get(i);
1554                 node.dependents.clear();
1555                 node.dependencies.clear();
1556             }
1557 
1558             // Builds up the dependents and dependencies for each node of the graph
1559             for (int i = 0; i < count; i++) {
1560                 final Node node = nodes.get(i);
1561 
1562                 final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams();
1563                 final int[] rules = layoutParams.mRules;
1564                 final int rulesCount = rulesFilter.length;
1565 
1566                 // Look only the the rules passed in parameter, this way we build only the
1567                 // dependencies for a specific set of rules
1568                 for (int j = 0; j < rulesCount; j++) {
1569                     final int rule = rules[rulesFilter[j]];
1570                     if (rule > 0) {
1571                         // The node this node depends on
1572                         final Node dependency = keyNodes.get(rule);
1573                         // Skip unknowns and self dependencies
1574                         if (dependency == null || dependency == node) {
1575                             continue;
1576                         }
1577                         // Add the current node as a dependent
1578                         dependency.dependents.put(node, this);
1579                         // Add a dependency to the current node
1580                         node.dependencies.put(rule, dependency);
1581                     }
1582                 }
1583             }
1584 
1585             final ArrayDeque<Node> roots = mRoots;
1586             roots.clear();
1587 
1588             // Finds all the roots in the graph: all nodes with no dependencies
1589             for (int i = 0; i < count; i++) {
1590                 final Node node = nodes.get(i);
1591                 if (node.dependencies.size() == 0) roots.addLast(node);
1592             }
1593 
1594             return roots;
1595         }
1596 
1597         /**
1598          * Prints the dependency graph for the specified rules.
1599          *
1600          * @param resources The context's resources to print the ids.
1601          * @param rules The list of rules to take into account.
1602          */
log(Resources resources, int... rules)1603         void log(Resources resources, int... rules) {
1604             final ArrayDeque<Node> roots = findRoots(rules);
1605             for (Node node : roots) {
1606                 printNode(resources, node);
1607             }
1608         }
1609 
printViewId(Resources resources, View view)1610         static void printViewId(Resources resources, View view) {
1611             if (view.getId() != View.NO_ID) {
1612                 d(LOG_TAG, resources.getResourceEntryName(view.getId()));
1613             } else {
1614                 d(LOG_TAG, "NO_ID");
1615             }
1616         }
1617 
appendViewId(Resources resources, Node node, StringBuilder buffer)1618         private static void appendViewId(Resources resources, Node node, StringBuilder buffer) {
1619             if (node.view.getId() != View.NO_ID) {
1620                 buffer.append(resources.getResourceEntryName(node.view.getId()));
1621             } else {
1622                 buffer.append("NO_ID");
1623             }
1624         }
1625 
printNode(Resources resources, Node node)1626         private static void printNode(Resources resources, Node node) {
1627             if (node.dependents.size() == 0) {
1628                 printViewId(resources, node.view);
1629             } else {
1630                 for (Node dependent : node.dependents.keySet()) {
1631                     StringBuilder buffer = new StringBuilder();
1632                     appendViewId(resources, node, buffer);
1633                     printdependents(resources, dependent, buffer);
1634                 }
1635             }
1636         }
1637 
printdependents(Resources resources, Node node, StringBuilder buffer)1638         private static void printdependents(Resources resources, Node node, StringBuilder buffer) {
1639             buffer.append(" -> ");
1640             appendViewId(resources, node, buffer);
1641 
1642             if (node.dependents.size() == 0) {
1643                 d(LOG_TAG, buffer.toString());
1644             } else {
1645                 for (Node dependent : node.dependents.keySet()) {
1646                     StringBuilder subBuffer = new StringBuilder(buffer);
1647                     printdependents(resources, dependent, subBuffer);
1648                 }
1649             }
1650         }
1651 
1652         /**
1653          * A node in the dependency graph. A node is a view, its list of dependencies
1654          * and its list of dependents.
1655          *
1656          * A node with no dependent is considered a root of the graph.
1657          */
1658         static class Node implements Poolable<Node> {
1659             /**
1660              * The view representing this node in the layout.
1661              */
1662             View view;
1663 
1664             /**
1665              * The list of dependents for this node; a dependent is a node
1666              * that needs this node to be processed first.
1667              */
1668             final HashMap<Node, DependencyGraph> dependents = new HashMap<Node, DependencyGraph>();
1669 
1670             /**
1671              * The list of dependencies for this node.
1672              */
1673             final SparseArray<Node> dependencies = new SparseArray<Node>();
1674 
1675             /*
1676              * START POOL IMPLEMENTATION
1677              */
1678             // The pool is static, so all nodes instances are shared across
1679             // activities, that's why we give it a rather high limit
1680             private static final int POOL_LIMIT = 100;
1681             private static final Pool<Node> sPool = Pools.synchronizedPool(
1682                     Pools.finitePool(new PoolableManager<Node>() {
1683                         public Node newInstance() {
1684                             return new Node();
1685                         }
1686 
1687                         public void onAcquired(Node element) {
1688                         }
1689 
1690                         public void onReleased(Node element) {
1691                         }
1692                     }, POOL_LIMIT)
1693             );
1694 
1695             private Node mNext;
1696             private boolean mIsPooled;
1697 
setNextPoolable(Node element)1698             public void setNextPoolable(Node element) {
1699                 mNext = element;
1700             }
1701 
getNextPoolable()1702             public Node getNextPoolable() {
1703                 return mNext;
1704             }
1705 
isPooled()1706             public boolean isPooled() {
1707                 return mIsPooled;
1708             }
1709 
setPooled(boolean isPooled)1710             public void setPooled(boolean isPooled) {
1711                 mIsPooled = isPooled;
1712             }
1713 
acquire(View view)1714             static Node acquire(View view) {
1715                 final Node node = sPool.acquire();
1716                 node.view = view;
1717 
1718                 return node;
1719             }
1720 
release()1721             void release() {
1722                 view = null;
1723                 dependents.clear();
1724                 dependencies.clear();
1725 
1726                 sPool.release(this);
1727             }
1728             /*
1729              * END POOL IMPLEMENTATION
1730              */
1731         }
1732     }
1733 }
1734