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