• 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 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.height == 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 lp)1106     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
1107         if (sPreserveMarginParamsInLayoutParamConversion) {
1108             if (lp instanceof LayoutParams) {
1109                 return new LayoutParams((LayoutParams) lp);
1110             } else if (lp instanceof MarginLayoutParams) {
1111                 return new LayoutParams((MarginLayoutParams) lp);
1112             }
1113         }
1114         return new LayoutParams(lp);
1115     }
1116 
1117     /** @hide */
1118     @Override
dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event)1119     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
1120         if (mTopToBottomLeftToRightSet == null) {
1121             mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator());
1122         }
1123 
1124         // sort children top-to-bottom and left-to-right
1125         for (int i = 0, count = getChildCount(); i < count; i++) {
1126             mTopToBottomLeftToRightSet.add(getChildAt(i));
1127         }
1128 
1129         for (View view : mTopToBottomLeftToRightSet) {
1130             if (view.getVisibility() == View.VISIBLE
1131                     && view.dispatchPopulateAccessibilityEvent(event)) {
1132                 mTopToBottomLeftToRightSet.clear();
1133                 return true;
1134             }
1135         }
1136 
1137         mTopToBottomLeftToRightSet.clear();
1138         return false;
1139     }
1140 
1141     @Override
getAccessibilityClassName()1142     public CharSequence getAccessibilityClassName() {
1143         return RelativeLayout.class.getName();
1144     }
1145 
1146     /**
1147      * Compares two views in left-to-right and top-to-bottom fashion.
1148      */
1149      private class TopToBottomLeftToRightComparator implements Comparator<View> {
compare(View first, View second)1150         public int compare(View first, View second) {
1151             // top - bottom
1152             int topDifference = first.getTop() - second.getTop();
1153             if (topDifference != 0) {
1154                 return topDifference;
1155             }
1156             // left - right
1157             int leftDifference = first.getLeft() - second.getLeft();
1158             if (leftDifference != 0) {
1159                 return leftDifference;
1160             }
1161             // break tie by height
1162             int heightDiference = first.getHeight() - second.getHeight();
1163             if (heightDiference != 0) {
1164                 return heightDiference;
1165             }
1166             // break tie by width
1167             int widthDiference = first.getWidth() - second.getWidth();
1168             if (widthDiference != 0) {
1169                 return widthDiference;
1170             }
1171             return 0;
1172         }
1173     }
1174 
1175     /**
1176      * Per-child layout information associated with RelativeLayout.
1177      *
1178      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignWithParentIfMissing
1179      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toLeftOf
1180      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toRightOf
1181      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_above
1182      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_below
1183      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBaseline
1184      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignLeft
1185      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignTop
1186      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignRight
1187      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBottom
1188      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentLeft
1189      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentTop
1190      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentRight
1191      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentBottom
1192      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent
1193      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal
1194      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical
1195      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toStartOf
1196      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toEndOf
1197      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignStart
1198      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignEnd
1199      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentStart
1200      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentEnd
1201      */
1202     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1203         @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = {
1204             @ViewDebug.IntToString(from = ABOVE,               to = "above"),
1205             @ViewDebug.IntToString(from = ALIGN_BASELINE,      to = "alignBaseline"),
1206             @ViewDebug.IntToString(from = ALIGN_BOTTOM,        to = "alignBottom"),
1207             @ViewDebug.IntToString(from = ALIGN_LEFT,          to = "alignLeft"),
1208             @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"),
1209             @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT,   to = "alignParentLeft"),
1210             @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT,  to = "alignParentRight"),
1211             @ViewDebug.IntToString(from = ALIGN_PARENT_TOP,    to = "alignParentTop"),
1212             @ViewDebug.IntToString(from = ALIGN_RIGHT,         to = "alignRight"),
1213             @ViewDebug.IntToString(from = ALIGN_TOP,           to = "alignTop"),
1214             @ViewDebug.IntToString(from = BELOW,               to = "below"),
1215             @ViewDebug.IntToString(from = CENTER_HORIZONTAL,   to = "centerHorizontal"),
1216             @ViewDebug.IntToString(from = CENTER_IN_PARENT,    to = "center"),
1217             @ViewDebug.IntToString(from = CENTER_VERTICAL,     to = "centerVertical"),
1218             @ViewDebug.IntToString(from = LEFT_OF,             to = "leftOf"),
1219             @ViewDebug.IntToString(from = RIGHT_OF,            to = "rightOf"),
1220             @ViewDebug.IntToString(from = ALIGN_START,         to = "alignStart"),
1221             @ViewDebug.IntToString(from = ALIGN_END,           to = "alignEnd"),
1222             @ViewDebug.IntToString(from = ALIGN_PARENT_START,  to = "alignParentStart"),
1223             @ViewDebug.IntToString(from = ALIGN_PARENT_END,    to = "alignParentEnd"),
1224             @ViewDebug.IntToString(from = START_OF,            to = "startOf"),
1225             @ViewDebug.IntToString(from = END_OF,              to = "endOf")
1226         }, mapping = {
1227             @ViewDebug.IntToString(from = TRUE, to = "true"),
1228             @ViewDebug.IntToString(from = 0,    to = "false/NO_ID")
1229         })
1230 
1231         private int[] mRules = new int[VERB_COUNT];
1232         private int[] mInitialRules = new int[VERB_COUNT];
1233 
1234         private int mLeft, mTop, mRight, mBottom;
1235 
1236         /**
1237          * Whether this view had any relative rules modified following the most
1238          * recent resolution of layout direction.
1239          */
1240         private boolean mNeedsLayoutResolution;
1241 
1242         private boolean mRulesChanged = false;
1243         private boolean mIsRtlCompatibilityMode = false;
1244 
1245         /**
1246          * When true, uses the parent as the anchor if the anchor doesn't exist or if
1247          * the anchor's visibility is GONE.
1248          */
1249         @ViewDebug.ExportedProperty(category = "layout")
1250         public boolean alignWithParent;
1251 
LayoutParams(Context c, AttributeSet attrs)1252         public LayoutParams(Context c, AttributeSet attrs) {
1253             super(c, attrs);
1254 
1255             TypedArray a = c.obtainStyledAttributes(attrs,
1256                     com.android.internal.R.styleable.RelativeLayout_Layout);
1257 
1258             final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
1259             mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
1260                     !c.getApplicationInfo().hasRtlSupport());
1261 
1262             final int[] rules = mRules;
1263             //noinspection MismatchedReadAndWriteOfArray
1264             final int[] initialRules = mInitialRules;
1265 
1266             final int N = a.getIndexCount();
1267             for (int i = 0; i < N; i++) {
1268                 int attr = a.getIndex(i);
1269                 switch (attr) {
1270                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:
1271                         alignWithParent = a.getBoolean(attr, false);
1272                         break;
1273                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
1274                         rules[LEFT_OF] = a.getResourceId(attr, 0);
1275                         break;
1276                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:
1277                         rules[RIGHT_OF] = a.getResourceId(attr, 0);
1278                         break;
1279                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:
1280                         rules[ABOVE] = a.getResourceId(attr, 0);
1281                         break;
1282                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below:
1283                         rules[BELOW] = a.getResourceId(attr, 0);
1284                         break;
1285                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline:
1286                         rules[ALIGN_BASELINE] = a.getResourceId(attr, 0);
1287                         break;
1288                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft:
1289                         rules[ALIGN_LEFT] = a.getResourceId(attr, 0);
1290                         break;
1291                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop:
1292                         rules[ALIGN_TOP] = a.getResourceId(attr, 0);
1293                         break;
1294                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight:
1295                         rules[ALIGN_RIGHT] = a.getResourceId(attr, 0);
1296                         break;
1297                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom:
1298                         rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0);
1299                         break;
1300                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft:
1301                         rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0;
1302                         break;
1303                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop:
1304                         rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0;
1305                         break;
1306                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight:
1307                         rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0;
1308                         break;
1309                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom:
1310                         rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0;
1311                         break;
1312                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent:
1313                         rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;
1314                         break;
1315                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal:
1316                         rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0;
1317                         break;
1318                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical:
1319                         rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;
1320                        break;
1321                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf:
1322                         rules[START_OF] = a.getResourceId(attr, 0);
1323                         break;
1324                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf:
1325                         rules[END_OF] = a.getResourceId(attr, 0);
1326                         break;
1327                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart:
1328                         rules[ALIGN_START] = a.getResourceId(attr, 0);
1329                         break;
1330                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd:
1331                         rules[ALIGN_END] = a.getResourceId(attr, 0);
1332                         break;
1333                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart:
1334                         rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0;
1335                         break;
1336                     case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd:
1337                         rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0;
1338                         break;
1339                 }
1340             }
1341             mRulesChanged = true;
1342             System.arraycopy(rules, LEFT_OF, initialRules, LEFT_OF, VERB_COUNT);
1343 
1344             a.recycle();
1345         }
1346 
LayoutParams(int w, int h)1347         public LayoutParams(int w, int h) {
1348             super(w, h);
1349         }
1350 
1351         /**
1352          * {@inheritDoc}
1353          */
LayoutParams(ViewGroup.LayoutParams source)1354         public LayoutParams(ViewGroup.LayoutParams source) {
1355             super(source);
1356         }
1357 
1358         /**
1359          * {@inheritDoc}
1360          */
LayoutParams(ViewGroup.MarginLayoutParams source)1361         public LayoutParams(ViewGroup.MarginLayoutParams source) {
1362             super(source);
1363         }
1364 
1365         /**
1366          * Copy constructor. Clones the width, height, margin values, and rules
1367          * of the source.
1368          *
1369          * @param source The layout params to copy from.
1370          */
LayoutParams(LayoutParams source)1371         public LayoutParams(LayoutParams source) {
1372             super(source);
1373 
1374             this.mIsRtlCompatibilityMode = source.mIsRtlCompatibilityMode;
1375             this.mRulesChanged = source.mRulesChanged;
1376             this.alignWithParent = source.alignWithParent;
1377 
1378             System.arraycopy(source.mRules, LEFT_OF, this.mRules, LEFT_OF, VERB_COUNT);
1379             System.arraycopy(
1380                     source.mInitialRules, LEFT_OF, this.mInitialRules, LEFT_OF, VERB_COUNT);
1381         }
1382 
1383         @Override
debug(String output)1384         public String debug(String output) {
1385             return output + "ViewGroup.LayoutParams={ width=" + sizeToString(width) +
1386                     ", height=" + sizeToString(height) + " }";
1387         }
1388 
1389         /**
1390          * Adds a layout rule to be interpreted by the RelativeLayout.
1391          * <p>
1392          * This method should only be used for verbs that don't refer to a
1393          * sibling (ex. {@link #ALIGN_RIGHT}) or take a boolean
1394          * value ({@link #TRUE} for true or 0 for false). To
1395          * specify a verb that takes a subject, use {@link #addRule(int, int)}.
1396          * <p>
1397          * If the rule is relative to the layout direction (ex.
1398          * {@link #ALIGN_PARENT_START}), then the layout direction must be
1399          * resolved using {@link #resolveLayoutDirection(int)} before calling
1400          * {@link #getRule(int)} an absolute rule (ex.
1401          * {@link #ALIGN_PARENT_LEFT}.
1402          *
1403          * @param verb a layout verb, such as {@link #ALIGN_PARENT_LEFT}
1404          * @see #addRule(int, int)
1405          * @see #removeRule(int)
1406          * @see #getRule(int)
1407          */
addRule(int verb)1408         public void addRule(int verb) {
1409             addRule(verb, TRUE);
1410         }
1411 
1412         /**
1413          * Adds a layout rule to be interpreted by the RelativeLayout.
1414          * <p>
1415          * Use this for verbs that refer to a sibling (ex.
1416          * {@link #ALIGN_RIGHT}) or take a boolean value (ex.
1417          * {@link #CENTER_IN_PARENT}).
1418          * <p>
1419          * If the rule is relative to the layout direction (ex.
1420          * {@link #START_OF}), then the layout direction must be resolved using
1421          * {@link #resolveLayoutDirection(int)} before calling
1422          * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}.
1423          *
1424          * @param verb a layout verb, such as {@link #ALIGN_RIGHT}
1425          * @param subject the ID of another view to use as an anchor, or a
1426          *                boolean value (represented as {@link #TRUE} for true
1427          *                or 0 for false)
1428          * @see #addRule(int)
1429          * @see #removeRule(int)
1430          * @see #getRule(int)
1431          */
addRule(int verb, int subject)1432         public void addRule(int verb, int subject) {
1433             // If we're removing a relative rule, we'll need to force layout
1434             // resolution the next time it's requested.
1435             if (!mNeedsLayoutResolution && isRelativeRule(verb)
1436                     && mInitialRules[verb] != 0 && subject == 0) {
1437                 mNeedsLayoutResolution = true;
1438             }
1439 
1440             mRules[verb] = subject;
1441             mInitialRules[verb] = subject;
1442             mRulesChanged = true;
1443         }
1444 
1445         /**
1446          * Removes a layout rule to be interpreted by the RelativeLayout.
1447          * <p>
1448          * If the rule is relative to the layout direction (ex.
1449          * {@link #START_OF}, {@link #ALIGN_PARENT_START}, etc.) then the
1450          * layout direction must be resolved using
1451          * {@link #resolveLayoutDirection(int)} before before calling
1452          * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}.
1453          *
1454          * @param verb One of the verbs defined by
1455          *        {@link android.widget.RelativeLayout RelativeLayout}, such as
1456          *         ALIGN_WITH_PARENT_LEFT.
1457          * @see #addRule(int)
1458          * @see #addRule(int, int)
1459          * @see #getRule(int)
1460          */
removeRule(int verb)1461         public void removeRule(int verb) {
1462             addRule(verb, 0);
1463         }
1464 
1465         /**
1466          * Returns the layout rule associated with a specific verb.
1467          *
1468          * @param verb one of the verbs defined by {@link RelativeLayout}, such
1469          *             as ALIGN_WITH_PARENT_LEFT
1470          * @return the id of another view to use as an anchor, a boolean value
1471          *         (represented as {@link RelativeLayout#TRUE} for true
1472          *         or 0 for false), or -1 for verbs that don't refer to another
1473          *         sibling (for example, ALIGN_WITH_PARENT_BOTTOM)
1474          * @see #addRule(int)
1475          * @see #addRule(int, int)
1476          */
getRule(int verb)1477         public int getRule(int verb) {
1478             return mRules[verb];
1479         }
1480 
hasRelativeRules()1481         private boolean hasRelativeRules() {
1482             return (mInitialRules[START_OF] != 0 || mInitialRules[END_OF] != 0 ||
1483                     mInitialRules[ALIGN_START] != 0 || mInitialRules[ALIGN_END] != 0 ||
1484                     mInitialRules[ALIGN_PARENT_START] != 0 || mInitialRules[ALIGN_PARENT_END] != 0);
1485         }
1486 
isRelativeRule(int rule)1487         private boolean isRelativeRule(int rule) {
1488             return rule == START_OF || rule == END_OF
1489                     || rule == ALIGN_START || rule == ALIGN_END
1490                     || rule == ALIGN_PARENT_START || rule == ALIGN_PARENT_END;
1491         }
1492 
1493         // The way we are resolving rules depends on the layout direction and if we are pre JB MR1
1494         // or not.
1495         //
1496         // If we are pre JB MR1 (said as "RTL compatibility mode"), "left"/"right" rules are having
1497         // predominance over any "start/end" rules that could have been defined. A special case:
1498         // if no "left"/"right" rule has been defined and "start"/"end" rules are defined then we
1499         // resolve those "start"/"end" rules to "left"/"right" respectively.
1500         //
1501         // If we are JB MR1+, then "start"/"end" rules are having predominance over "left"/"right"
1502         // rules. If no "start"/"end" rule is defined then we use "left"/"right" rules.
1503         //
1504         // In all cases, the result of the resolution should clear the "start"/"end" rules to leave
1505         // only the "left"/"right" rules at the end.
resolveRules(int layoutDirection)1506         private void resolveRules(int layoutDirection) {
1507             final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL);
1508 
1509             // Reset to initial state
1510             System.arraycopy(mInitialRules, LEFT_OF, mRules, LEFT_OF, VERB_COUNT);
1511 
1512             // Apply rules depending on direction and if we are in RTL compatibility mode
1513             if (mIsRtlCompatibilityMode) {
1514                 if (mRules[ALIGN_START] != 0) {
1515                     if (mRules[ALIGN_LEFT] == 0) {
1516                         // "left" rule is not defined but "start" rule is: use the "start" rule as
1517                         // the "left" rule
1518                         mRules[ALIGN_LEFT] = mRules[ALIGN_START];
1519                     }
1520                     mRules[ALIGN_START] = 0;
1521                 }
1522 
1523                 if (mRules[ALIGN_END] != 0) {
1524                     if (mRules[ALIGN_RIGHT] == 0) {
1525                         // "right" rule is not defined but "end" rule is: use the "end" rule as the
1526                         // "right" rule
1527                         mRules[ALIGN_RIGHT] = mRules[ALIGN_END];
1528                     }
1529                     mRules[ALIGN_END] = 0;
1530                 }
1531 
1532                 if (mRules[START_OF] != 0) {
1533                     if (mRules[LEFT_OF] == 0) {
1534                         // "left" rule is not defined but "start" rule is: use the "start" rule as
1535                         // the "left" rule
1536                         mRules[LEFT_OF] = mRules[START_OF];
1537                     }
1538                     mRules[START_OF] = 0;
1539                 }
1540 
1541                 if (mRules[END_OF] != 0) {
1542                     if (mRules[RIGHT_OF] == 0) {
1543                         // "right" rule is not defined but "end" rule is: use the "end" rule as the
1544                         // "right" rule
1545                         mRules[RIGHT_OF] = mRules[END_OF];
1546                     }
1547                     mRules[END_OF] = 0;
1548                 }
1549 
1550                 if (mRules[ALIGN_PARENT_START] != 0) {
1551                     if (mRules[ALIGN_PARENT_LEFT] == 0) {
1552                         // "left" rule is not defined but "start" rule is: use the "start" rule as
1553                         // the "left" rule
1554                         mRules[ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START];
1555                     }
1556                     mRules[ALIGN_PARENT_START] = 0;
1557                 }
1558 
1559                 if (mRules[ALIGN_PARENT_END] != 0) {
1560                     if (mRules[ALIGN_PARENT_RIGHT] == 0) {
1561                         // "right" rule is not defined but "end" rule is: use the "end" rule as the
1562                         // "right" rule
1563                         mRules[ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END];
1564                     }
1565                     mRules[ALIGN_PARENT_END] = 0;
1566                 }
1567             } else {
1568                 // JB MR1+ case
1569                 if ((mRules[ALIGN_START] != 0 || mRules[ALIGN_END] != 0) &&
1570                         (mRules[ALIGN_LEFT] != 0 || mRules[ALIGN_RIGHT] != 0)) {
1571                     // "start"/"end" rules take precedence over "left"/"right" rules
1572                     mRules[ALIGN_LEFT] = 0;
1573                     mRules[ALIGN_RIGHT] = 0;
1574                 }
1575                 if (mRules[ALIGN_START] != 0) {
1576                     // "start" rule resolved to "left" or "right" depending on the direction
1577                     mRules[isLayoutRtl ? ALIGN_RIGHT : ALIGN_LEFT] = mRules[ALIGN_START];
1578                     mRules[ALIGN_START] = 0;
1579                 }
1580                 if (mRules[ALIGN_END] != 0) {
1581                     // "end" rule resolved to "left" or "right" depending on the direction
1582                     mRules[isLayoutRtl ? ALIGN_LEFT : ALIGN_RIGHT] = mRules[ALIGN_END];
1583                     mRules[ALIGN_END] = 0;
1584                 }
1585 
1586                 if ((mRules[START_OF] != 0 || mRules[END_OF] != 0) &&
1587                         (mRules[LEFT_OF] != 0 || mRules[RIGHT_OF] != 0)) {
1588                     // "start"/"end" rules take precedence over "left"/"right" rules
1589                     mRules[LEFT_OF] = 0;
1590                     mRules[RIGHT_OF] = 0;
1591                 }
1592                 if (mRules[START_OF] != 0) {
1593                     // "start" rule resolved to "left" or "right" depending on the direction
1594                     mRules[isLayoutRtl ? RIGHT_OF : LEFT_OF] = mRules[START_OF];
1595                     mRules[START_OF] = 0;
1596                 }
1597                 if (mRules[END_OF] != 0) {
1598                     // "end" rule resolved to "left" or "right" depending on the direction
1599                     mRules[isLayoutRtl ? LEFT_OF : RIGHT_OF] = mRules[END_OF];
1600                     mRules[END_OF] = 0;
1601                 }
1602 
1603                 if ((mRules[ALIGN_PARENT_START] != 0 || mRules[ALIGN_PARENT_END] != 0) &&
1604                         (mRules[ALIGN_PARENT_LEFT] != 0 || mRules[ALIGN_PARENT_RIGHT] != 0)) {
1605                     // "start"/"end" rules take precedence over "left"/"right" rules
1606                     mRules[ALIGN_PARENT_LEFT] = 0;
1607                     mRules[ALIGN_PARENT_RIGHT] = 0;
1608                 }
1609                 if (mRules[ALIGN_PARENT_START] != 0) {
1610                     // "start" rule resolved to "left" or "right" depending on the direction
1611                     mRules[isLayoutRtl ? ALIGN_PARENT_RIGHT : ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START];
1612                     mRules[ALIGN_PARENT_START] = 0;
1613                 }
1614                 if (mRules[ALIGN_PARENT_END] != 0) {
1615                     // "end" rule resolved to "left" or "right" depending on the direction
1616                     mRules[isLayoutRtl ? ALIGN_PARENT_LEFT : ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END];
1617                     mRules[ALIGN_PARENT_END] = 0;
1618                 }
1619             }
1620 
1621             mRulesChanged = false;
1622             mNeedsLayoutResolution = false;
1623         }
1624 
1625         /**
1626          * Retrieves a complete list of all supported rules, where the index is the rule
1627          * verb, and the element value is the value specified, or "false" if it was never
1628          * set. If there are relative rules defined (*_START / *_END), they will be resolved
1629          * depending on the layout direction.
1630          *
1631          * @param layoutDirection the direction of the layout.
1632          *                        Should be either {@link View#LAYOUT_DIRECTION_LTR}
1633          *                        or {@link View#LAYOUT_DIRECTION_RTL}
1634          * @return the supported rules
1635          * @see #addRule(int, int)
1636          *
1637          * @hide
1638          */
getRules(int layoutDirection)1639         public int[] getRules(int layoutDirection) {
1640             resolveLayoutDirection(layoutDirection);
1641             return mRules;
1642         }
1643 
1644         /**
1645          * Retrieves a complete list of all supported rules, where the index is the rule
1646          * verb, and the element value is the value specified, or "false" if it was never
1647          * set. There will be no resolution of relative rules done.
1648          *
1649          * @return the supported rules
1650          * @see #addRule(int, int)
1651          */
getRules()1652         public int[] getRules() {
1653             return mRules;
1654         }
1655 
1656         /**
1657          * This will be called by {@link android.view.View#requestLayout()} to
1658          * resolve layout parameters that are relative to the layout direction.
1659          * <p>
1660          * After this method is called, any rules using layout-relative verbs
1661          * (ex. {@link #START_OF}) previously added via {@link #addRule(int)}
1662          * may only be accessed via their resolved absolute verbs (ex.
1663          * {@link #LEFT_OF}).
1664          */
1665         @Override
resolveLayoutDirection(int layoutDirection)1666         public void resolveLayoutDirection(int layoutDirection) {
1667             if (shouldResolveLayoutDirection(layoutDirection)) {
1668                 resolveRules(layoutDirection);
1669             }
1670 
1671             // This will set the layout direction.
1672             super.resolveLayoutDirection(layoutDirection);
1673         }
1674 
shouldResolveLayoutDirection(int layoutDirection)1675         private boolean shouldResolveLayoutDirection(int layoutDirection) {
1676             return (mNeedsLayoutResolution || hasRelativeRules())
1677                     && (mRulesChanged || layoutDirection != getLayoutDirection());
1678         }
1679 
1680         /** @hide */
1681         @Override
encodeProperties(@onNull ViewHierarchyEncoder encoder)1682         protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
1683             super.encodeProperties(encoder);
1684             encoder.addProperty("layout:alignWithParent", alignWithParent);
1685         }
1686     }
1687 
1688     private static class DependencyGraph {
1689         /**
1690          * List of all views in the graph.
1691          */
1692         private ArrayList<Node> mNodes = new ArrayList<Node>();
1693 
1694         /**
1695          * List of nodes in the graph. Each node is identified by its
1696          * view id (see View#getId()).
1697          */
1698         private SparseArray<Node> mKeyNodes = new SparseArray<Node>();
1699 
1700         /**
1701          * Temporary data structure used to build the list of roots
1702          * for this graph.
1703          */
1704         private ArrayDeque<Node> mRoots = new ArrayDeque<Node>();
1705 
1706         /**
1707          * Clears the graph.
1708          */
clear()1709         void clear() {
1710             final ArrayList<Node> nodes = mNodes;
1711             final int count = nodes.size();
1712 
1713             for (int i = 0; i < count; i++) {
1714                 nodes.get(i).release();
1715             }
1716             nodes.clear();
1717 
1718             mKeyNodes.clear();
1719             mRoots.clear();
1720         }
1721 
1722         /**
1723          * Adds a view to the graph.
1724          *
1725          * @param view The view to be added as a node to the graph.
1726          */
add(View view)1727         void add(View view) {
1728             final int id = view.getId();
1729             final Node node = Node.acquire(view);
1730 
1731             if (id != View.NO_ID) {
1732                 mKeyNodes.put(id, node);
1733             }
1734 
1735             mNodes.add(node);
1736         }
1737 
1738         /**
1739          * Builds a sorted list of views. The sorting order depends on the dependencies
1740          * between the view. For instance, if view C needs view A to be processed first
1741          * and view A needs view B to be processed first, the dependency graph
1742          * is: B -> A -> C. The sorted array will contain views B, A and C in this order.
1743          *
1744          * @param sorted The sorted list of views. The length of this array must
1745          *        be equal to getChildCount().
1746          * @param rules The list of rules to take into account.
1747          */
getSortedViews(View[] sorted, int... rules)1748         void getSortedViews(View[] sorted, int... rules) {
1749             final ArrayDeque<Node> roots = findRoots(rules);
1750             int index = 0;
1751 
1752             Node node;
1753             while ((node = roots.pollLast()) != null) {
1754                 final View view = node.view;
1755                 final int key = view.getId();
1756 
1757                 sorted[index++] = view;
1758 
1759                 final ArrayMap<Node, DependencyGraph> dependents = node.dependents;
1760                 final int count = dependents.size();
1761                 for (int i = 0; i < count; i++) {
1762                     final Node dependent = dependents.keyAt(i);
1763                     final SparseArray<Node> dependencies = dependent.dependencies;
1764 
1765                     dependencies.remove(key);
1766                     if (dependencies.size() == 0) {
1767                         roots.add(dependent);
1768                     }
1769                 }
1770             }
1771 
1772             if (index < sorted.length) {
1773                 throw new IllegalStateException("Circular dependencies cannot exist"
1774                         + " in RelativeLayout");
1775             }
1776         }
1777 
1778         /**
1779          * Finds the roots of the graph. A root is a node with no dependency and
1780          * with [0..n] dependents.
1781          *
1782          * @param rulesFilter The list of rules to consider when building the
1783          *        dependencies
1784          *
1785          * @return A list of node, each being a root of the graph
1786          */
findRoots(int[] rulesFilter)1787         private ArrayDeque<Node> findRoots(int[] rulesFilter) {
1788             final SparseArray<Node> keyNodes = mKeyNodes;
1789             final ArrayList<Node> nodes = mNodes;
1790             final int count = nodes.size();
1791 
1792             // Find roots can be invoked several times, so make sure to clear
1793             // all dependents and dependencies before running the algorithm
1794             for (int i = 0; i < count; i++) {
1795                 final Node node = nodes.get(i);
1796                 node.dependents.clear();
1797                 node.dependencies.clear();
1798             }
1799 
1800             // Builds up the dependents and dependencies for each node of the graph
1801             for (int i = 0; i < count; i++) {
1802                 final Node node = nodes.get(i);
1803 
1804                 final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams();
1805                 final int[] rules = layoutParams.mRules;
1806                 final int rulesCount = rulesFilter.length;
1807 
1808                 // Look only the the rules passed in parameter, this way we build only the
1809                 // dependencies for a specific set of rules
1810                 for (int j = 0; j < rulesCount; j++) {
1811                     final int rule = rules[rulesFilter[j]];
1812                     if (rule > 0) {
1813                         // The node this node depends on
1814                         final Node dependency = keyNodes.get(rule);
1815                         // Skip unknowns and self dependencies
1816                         if (dependency == null || dependency == node) {
1817                             continue;
1818                         }
1819                         // Add the current node as a dependent
1820                         dependency.dependents.put(node, this);
1821                         // Add a dependency to the current node
1822                         node.dependencies.put(rule, dependency);
1823                     }
1824                 }
1825             }
1826 
1827             final ArrayDeque<Node> roots = mRoots;
1828             roots.clear();
1829 
1830             // Finds all the roots in the graph: all nodes with no dependencies
1831             for (int i = 0; i < count; i++) {
1832                 final Node node = nodes.get(i);
1833                 if (node.dependencies.size() == 0) roots.addLast(node);
1834             }
1835 
1836             return roots;
1837         }
1838 
1839         /**
1840          * A node in the dependency graph. A node is a view, its list of dependencies
1841          * and its list of dependents.
1842          *
1843          * A node with no dependent is considered a root of the graph.
1844          */
1845         static class Node {
1846             /**
1847              * The view representing this node in the layout.
1848              */
1849             View view;
1850 
1851             /**
1852              * The list of dependents for this node; a dependent is a node
1853              * that needs this node to be processed first.
1854              */
1855             final ArrayMap<Node, DependencyGraph> dependents =
1856                     new ArrayMap<Node, DependencyGraph>();
1857 
1858             /**
1859              * The list of dependencies for this node.
1860              */
1861             final SparseArray<Node> dependencies = new SparseArray<Node>();
1862 
1863             /*
1864              * START POOL IMPLEMENTATION
1865              */
1866             // The pool is static, so all nodes instances are shared across
1867             // activities, that's why we give it a rather high limit
1868             private static final int POOL_LIMIT = 100;
1869             private static final SynchronizedPool<Node> sPool =
1870                     new SynchronizedPool<Node>(POOL_LIMIT);
1871 
acquire(View view)1872             static Node acquire(View view) {
1873                 Node node = sPool.acquire();
1874                 if (node == null) {
1875                     node = new Node();
1876                 }
1877                 node.view = view;
1878                 return node;
1879             }
1880 
release()1881             void release() {
1882                 view = null;
1883                 dependents.clear();
1884                 dependencies.clear();
1885 
1886                 sPool.release(this);
1887             }
1888             /*
1889              * END POOL IMPLEMENTATION
1890              */
1891         }
1892     }
1893 }
1894