blob: bbdf15c871bb93688078c4ae5890869e085599b5 [file] [log] [blame]
Justin Klaassen10d07c82017-09-15 17:58:39 -04001/*
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
17package android.widget;
18
19import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
20
21import android.annotation.NonNull;
22import android.content.Context;
23import android.content.res.TypedArray;
24import android.graphics.Rect;
25import android.os.Build;
26import android.util.ArrayMap;
27import android.util.AttributeSet;
28import android.util.Pools.SynchronizedPool;
29import android.util.SparseArray;
30import android.view.Gravity;
31import android.view.View;
32import android.view.ViewDebug;
33import android.view.ViewGroup;
34import android.view.ViewHierarchyEncoder;
35import android.view.accessibility.AccessibilityEvent;
36import android.widget.RemoteViews.RemoteView;
37
38import com.android.internal.R;
39
40import java.util.ArrayDeque;
41import java.util.ArrayList;
42import java.util.Comparator;
43import java.util.SortedSet;
44import java.util.TreeSet;
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
84public 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
235 public RelativeLayout(Context context) {
236 this(context, null);
237 }
238
239 public RelativeLayout(Context context, AttributeSet attrs) {
240 this(context, attrs, 0);
241 }
242
243 public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
244 this(context, attrs, defStyleAttr, 0);
245 }
246
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
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
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
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
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 */
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
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
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
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
354 public int getBaseline() {
355 return mBaselineView != null ? mBaselineView.getBaseline() : super.getBaseline();
356 }
357
358 @Override
359 public void requestLayout() {
360 super.requestLayout();
361 mDirtyHierarchy = true;
362 }
363
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
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 */
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 */
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
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 */
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
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 positionAtEdge(child, params, myWidth);
837 }
838 return true;
839 } else {
840 // This is the default case. For RTL we start from the right and for LTR we start
841 // from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL.
842 positionAtEdge(child, params, myWidth);
843 }
844 }
845 return rules[ALIGN_PARENT_END] != 0;
846 }
847
848 private void positionAtEdge(View child, LayoutParams params, int myWidth) {
849 if (isLayoutRtl()) {
850 params.mRight = myWidth - mPaddingRight - params.rightMargin;
851 params.mLeft = params.mRight - child.getMeasuredWidth();
852 } else {
853 params.mLeft = mPaddingLeft + params.leftMargin;
854 params.mRight = params.mLeft + child.getMeasuredWidth();
855 }
856 }
857
858 private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
859 boolean wrapContent) {
860
861 int[] rules = params.getRules();
862
863 if (params.mTop == VALUE_NOT_SET && params.mBottom != VALUE_NOT_SET) {
864 // Bottom is fixed, but top varies
865 params.mTop = params.mBottom - child.getMeasuredHeight();
866 } else if (params.mTop != VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) {
867 // Top is fixed, but bottom varies
868 params.mBottom = params.mTop + child.getMeasuredHeight();
869 } else if (params.mTop == VALUE_NOT_SET && params.mBottom == VALUE_NOT_SET) {
870 // Both top and bottom vary
871 if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
872 if (!wrapContent) {
873 centerVertical(child, params, myHeight);
874 } else {
875 params.mTop = mPaddingTop + params.topMargin;
876 params.mBottom = params.mTop + child.getMeasuredHeight();
877 }
878 return true;
879 } else {
880 params.mTop = mPaddingTop + params.topMargin;
881 params.mBottom = params.mTop + child.getMeasuredHeight();
882 }
883 }
884 return rules[ALIGN_PARENT_BOTTOM] != 0;
885 }
886
887 private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
888 RelativeLayout.LayoutParams anchorParams;
889
890 // VALUE_NOT_SET indicates a "soft requirement" in that direction. For example:
891 // left=10, right=VALUE_NOT_SET means the view must start at 10, but can go as far as it
892 // wants to the right
893 // left=VALUE_NOT_SET, right=10 means the view must end at 10, but can go as far as it
894 // wants to the left
895 // left=10, right=20 means the left and right ends are both fixed
896 childParams.mLeft = VALUE_NOT_SET;
897 childParams.mRight = VALUE_NOT_SET;
898
899 anchorParams = getRelatedViewParams(rules, LEFT_OF);
900 if (anchorParams != null) {
901 childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
902 childParams.rightMargin);
903 } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) {
904 if (myWidth >= 0) {
905 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
906 }
907 }
908
909 anchorParams = getRelatedViewParams(rules, RIGHT_OF);
910 if (anchorParams != null) {
911 childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin +
912 childParams.leftMargin);
913 } else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) {
914 childParams.mLeft = mPaddingLeft + childParams.leftMargin;
915 }
916
917 anchorParams = getRelatedViewParams(rules, ALIGN_LEFT);
918 if (anchorParams != null) {
919 childParams.mLeft = anchorParams.mLeft + childParams.leftMargin;
920 } else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) {
921 childParams.mLeft = mPaddingLeft + childParams.leftMargin;
922 }
923
924 anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT);
925 if (anchorParams != null) {
926 childParams.mRight = anchorParams.mRight - childParams.rightMargin;
927 } else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) {
928 if (myWidth >= 0) {
929 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
930 }
931 }
932
933 if (0 != rules[ALIGN_PARENT_LEFT]) {
934 childParams.mLeft = mPaddingLeft + childParams.leftMargin;
935 }
936
937 if (0 != rules[ALIGN_PARENT_RIGHT]) {
938 if (myWidth >= 0) {
939 childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
940 }
941 }
942 }
943
944 private void applyVerticalSizeRules(LayoutParams childParams, int myHeight, int myBaseline) {
945 final int[] rules = childParams.getRules();
946
947 // Baseline alignment overrides any explicitly specified top or bottom.
948 int baselineOffset = getRelatedViewBaselineOffset(rules);
949 if (baselineOffset != -1) {
950 if (myBaseline != -1) {
951 baselineOffset -= myBaseline;
952 }
953 childParams.mTop = baselineOffset;
954 childParams.mBottom = VALUE_NOT_SET;
955 return;
956 }
957
958 RelativeLayout.LayoutParams anchorParams;
959
960 childParams.mTop = VALUE_NOT_SET;
961 childParams.mBottom = VALUE_NOT_SET;
962
963 anchorParams = getRelatedViewParams(rules, ABOVE);
964 if (anchorParams != null) {
965 childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin +
966 childParams.bottomMargin);
967 } else if (childParams.alignWithParent && rules[ABOVE] != 0) {
968 if (myHeight >= 0) {
969 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
970 }
971 }
972
973 anchorParams = getRelatedViewParams(rules, BELOW);
974 if (anchorParams != null) {
975 childParams.mTop = anchorParams.mBottom + (anchorParams.bottomMargin +
976 childParams.topMargin);
977 } else if (childParams.alignWithParent && rules[BELOW] != 0) {
978 childParams.mTop = mPaddingTop + childParams.topMargin;
979 }
980
981 anchorParams = getRelatedViewParams(rules, ALIGN_TOP);
982 if (anchorParams != null) {
983 childParams.mTop = anchorParams.mTop + childParams.topMargin;
984 } else if (childParams.alignWithParent && rules[ALIGN_TOP] != 0) {
985 childParams.mTop = mPaddingTop + childParams.topMargin;
986 }
987
988 anchorParams = getRelatedViewParams(rules, ALIGN_BOTTOM);
989 if (anchorParams != null) {
990 childParams.mBottom = anchorParams.mBottom - childParams.bottomMargin;
991 } else if (childParams.alignWithParent && rules[ALIGN_BOTTOM] != 0) {
992 if (myHeight >= 0) {
993 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
994 }
995 }
996
997 if (0 != rules[ALIGN_PARENT_TOP]) {
998 childParams.mTop = mPaddingTop + childParams.topMargin;
999 }
1000
1001 if (0 != rules[ALIGN_PARENT_BOTTOM]) {
1002 if (myHeight >= 0) {
1003 childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
1004 }
1005 }
1006 }
1007
1008 private View getRelatedView(int[] rules, int relation) {
1009 int id = rules[relation];
1010 if (id != 0) {
1011 DependencyGraph.Node node = mGraph.mKeyNodes.get(id);
1012 if (node == null) return null;
1013 View v = node.view;
1014
1015 // Find the first non-GONE view up the chain
1016 while (v.getVisibility() == View.GONE) {
1017 rules = ((LayoutParams) v.getLayoutParams()).getRules(v.getLayoutDirection());
1018 node = mGraph.mKeyNodes.get((rules[relation]));
1019 // ignore self dependency. for more info look in git commit: da3003
1020 if (node == null || v == node.view) return null;
1021 v = node.view;
1022 }
1023
1024 return v;
1025 }
1026
1027 return null;
1028 }
1029
1030 private LayoutParams getRelatedViewParams(int[] rules, int relation) {
1031 View v = getRelatedView(rules, relation);
1032 if (v != null) {
1033 ViewGroup.LayoutParams params = v.getLayoutParams();
1034 if (params instanceof LayoutParams) {
1035 return (LayoutParams) v.getLayoutParams();
1036 }
1037 }
1038 return null;
1039 }
1040
1041 private int getRelatedViewBaselineOffset(int[] rules) {
1042 final View v = getRelatedView(rules, ALIGN_BASELINE);
1043 if (v != null) {
1044 final int baseline = v.getBaseline();
1045 if (baseline != -1) {
1046 final ViewGroup.LayoutParams params = v.getLayoutParams();
1047 if (params instanceof LayoutParams) {
1048 final LayoutParams anchorParams = (LayoutParams) v.getLayoutParams();
1049 return anchorParams.mTop + baseline;
1050 }
1051 }
1052 }
1053 return -1;
1054 }
1055
1056 private static void centerHorizontal(View child, LayoutParams params, int myWidth) {
1057 int childWidth = child.getMeasuredWidth();
1058 int left = (myWidth - childWidth) / 2;
1059
1060 params.mLeft = left;
1061 params.mRight = left + childWidth;
1062 }
1063
1064 private static void centerVertical(View child, LayoutParams params, int myHeight) {
1065 int childHeight = child.getMeasuredHeight();
1066 int top = (myHeight - childHeight) / 2;
1067
1068 params.mTop = top;
1069 params.mBottom = top + childHeight;
1070 }
1071
1072 @Override
1073 protected void onLayout(boolean changed, int l, int t, int r, int b) {
1074 // The layout has actually already been performed and the positions
1075 // cached. Apply the cached values to the children.
1076 final int count = getChildCount();
1077
1078 for (int i = 0; i < count; i++) {
1079 View child = getChildAt(i);
1080 if (child.getVisibility() != GONE) {
1081 RelativeLayout.LayoutParams st =
1082 (RelativeLayout.LayoutParams) child.getLayoutParams();
1083 child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
1084 }
1085 }
1086 }
1087
1088 @Override
1089 public LayoutParams generateLayoutParams(AttributeSet attrs) {
1090 return new RelativeLayout.LayoutParams(getContext(), attrs);
1091 }
1092
1093 /**
1094 * Returns a set of layout parameters with a width of
1095 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT},
1096 * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
1097 */
1098 @Override
1099 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1100 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
1101 }
1102
1103 // Override to allow type-checking of LayoutParams.
1104 @Override
1105 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1106 return p instanceof RelativeLayout.LayoutParams;
1107 }
1108
1109 @Override
1110 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
1111 if (sPreserveMarginParamsInLayoutParamConversion) {
1112 if (lp instanceof LayoutParams) {
1113 return new LayoutParams((LayoutParams) lp);
1114 } else if (lp instanceof MarginLayoutParams) {
1115 return new LayoutParams((MarginLayoutParams) lp);
1116 }
1117 }
1118 return new LayoutParams(lp);
1119 }
1120
1121 /** @hide */
1122 @Override
1123 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
1124 if (mTopToBottomLeftToRightSet == null) {
1125 mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator());
1126 }
1127
1128 // sort children top-to-bottom and left-to-right
1129 for (int i = 0, count = getChildCount(); i < count; i++) {
1130 mTopToBottomLeftToRightSet.add(getChildAt(i));
1131 }
1132
1133 for (View view : mTopToBottomLeftToRightSet) {
1134 if (view.getVisibility() == View.VISIBLE
1135 && view.dispatchPopulateAccessibilityEvent(event)) {
1136 mTopToBottomLeftToRightSet.clear();
1137 return true;
1138 }
1139 }
1140
1141 mTopToBottomLeftToRightSet.clear();
1142 return false;
1143 }
1144
1145 @Override
1146 public CharSequence getAccessibilityClassName() {
1147 return RelativeLayout.class.getName();
1148 }
1149
1150 /**
1151 * Compares two views in left-to-right and top-to-bottom fashion.
1152 */
1153 private class TopToBottomLeftToRightComparator implements Comparator<View> {
1154 public int compare(View first, View second) {
1155 // top - bottom
1156 int topDifference = first.getTop() - second.getTop();
1157 if (topDifference != 0) {
1158 return topDifference;
1159 }
1160 // left - right
1161 int leftDifference = first.getLeft() - second.getLeft();
1162 if (leftDifference != 0) {
1163 return leftDifference;
1164 }
1165 // break tie by height
1166 int heightDiference = first.getHeight() - second.getHeight();
1167 if (heightDiference != 0) {
1168 return heightDiference;
1169 }
1170 // break tie by width
1171 int widthDiference = first.getWidth() - second.getWidth();
1172 if (widthDiference != 0) {
1173 return widthDiference;
1174 }
1175 return 0;
1176 }
1177 }
1178
1179 /**
1180 * Specifies how a view is positioned within a {@link RelativeLayout}.
1181 * The relative layout containing the view uses the value of these layout parameters to
1182 * determine where to position the view on the screen. If the view is not contained
1183 * within a relative layout, these attributes are ignored.
1184 *
Justin Klaassen4d01eea2018-04-03 23:21:57 -04001185 * See the <a href="/guide/topics/ui/layout/relative.html">
Justin Klaassen10d07c82017-09-15 17:58:39 -04001186 * Relative Layout</a> guide for example code demonstrating how to use relative layout’s
1187 * layout parameters in a layout XML.
1188 *
1189 * To learn more about layout parameters and how they differ from typical view attributes,
Justin Klaassen4d01eea2018-04-03 23:21:57 -04001190 * see the <a href="/guide/topics/ui/declaring-layout.html#attributes">
Justin Klaassen10d07c82017-09-15 17:58:39 -04001191 * Layouts guide</a>.
1192 *
1193 *
1194 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignWithParentIfMissing
1195 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toLeftOf
1196 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toRightOf
1197 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_above
1198 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_below
1199 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBaseline
1200 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignLeft
1201 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignTop
1202 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignRight
1203 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBottom
1204 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentLeft
1205 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentTop
1206 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentRight
1207 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentBottom
1208 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent
1209 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal
1210 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical
1211 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toStartOf
1212 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toEndOf
1213 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignStart
1214 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignEnd
1215 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentStart
1216 * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentEnd
1217 */
1218 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1219 @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = {
1220 @ViewDebug.IntToString(from = ABOVE, to = "above"),
1221 @ViewDebug.IntToString(from = ALIGN_BASELINE, to = "alignBaseline"),
1222 @ViewDebug.IntToString(from = ALIGN_BOTTOM, to = "alignBottom"),
1223 @ViewDebug.IntToString(from = ALIGN_LEFT, to = "alignLeft"),
1224 @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"),
1225 @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT, to = "alignParentLeft"),
1226 @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT, to = "alignParentRight"),
1227 @ViewDebug.IntToString(from = ALIGN_PARENT_TOP, to = "alignParentTop"),
1228 @ViewDebug.IntToString(from = ALIGN_RIGHT, to = "alignRight"),
1229 @ViewDebug.IntToString(from = ALIGN_TOP, to = "alignTop"),
1230 @ViewDebug.IntToString(from = BELOW, to = "below"),
1231 @ViewDebug.IntToString(from = CENTER_HORIZONTAL, to = "centerHorizontal"),
1232 @ViewDebug.IntToString(from = CENTER_IN_PARENT, to = "center"),
1233 @ViewDebug.IntToString(from = CENTER_VERTICAL, to = "centerVertical"),
1234 @ViewDebug.IntToString(from = LEFT_OF, to = "leftOf"),
1235 @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf"),
1236 @ViewDebug.IntToString(from = ALIGN_START, to = "alignStart"),
1237 @ViewDebug.IntToString(from = ALIGN_END, to = "alignEnd"),
1238 @ViewDebug.IntToString(from = ALIGN_PARENT_START, to = "alignParentStart"),
1239 @ViewDebug.IntToString(from = ALIGN_PARENT_END, to = "alignParentEnd"),
1240 @ViewDebug.IntToString(from = START_OF, to = "startOf"),
1241 @ViewDebug.IntToString(from = END_OF, to = "endOf")
1242 }, mapping = {
1243 @ViewDebug.IntToString(from = TRUE, to = "true"),
1244 @ViewDebug.IntToString(from = 0, to = "false/NO_ID")
1245 })
1246
1247 private int[] mRules = new int[VERB_COUNT];
1248 private int[] mInitialRules = new int[VERB_COUNT];
1249
1250 private int mLeft, mTop, mRight, mBottom;
1251
1252 /**
1253 * Whether this view had any relative rules modified following the most
1254 * recent resolution of layout direction.
1255 */
1256 private boolean mNeedsLayoutResolution;
1257
1258 private boolean mRulesChanged = false;
1259 private boolean mIsRtlCompatibilityMode = false;
1260
1261 /**
1262 * When true, uses the parent as the anchor if the anchor doesn't exist or if
1263 * the anchor's visibility is GONE.
1264 */
1265 @ViewDebug.ExportedProperty(category = "layout")
1266 public boolean alignWithParent;
1267
1268 public LayoutParams(Context c, AttributeSet attrs) {
1269 super(c, attrs);
1270
1271 TypedArray a = c.obtainStyledAttributes(attrs,
1272 com.android.internal.R.styleable.RelativeLayout_Layout);
1273
1274 final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
1275 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
1276 !c.getApplicationInfo().hasRtlSupport());
1277
1278 final int[] rules = mRules;
1279 //noinspection MismatchedReadAndWriteOfArray
1280 final int[] initialRules = mInitialRules;
1281
1282 final int N = a.getIndexCount();
1283 for (int i = 0; i < N; i++) {
1284 int attr = a.getIndex(i);
1285 switch (attr) {
1286 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:
1287 alignWithParent = a.getBoolean(attr, false);
1288 break;
1289 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
1290 rules[LEFT_OF] = a.getResourceId(attr, 0);
1291 break;
1292 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:
1293 rules[RIGHT_OF] = a.getResourceId(attr, 0);
1294 break;
1295 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:
1296 rules[ABOVE] = a.getResourceId(attr, 0);
1297 break;
1298 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below:
1299 rules[BELOW] = a.getResourceId(attr, 0);
1300 break;
1301 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline:
1302 rules[ALIGN_BASELINE] = a.getResourceId(attr, 0);
1303 break;
1304 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft:
1305 rules[ALIGN_LEFT] = a.getResourceId(attr, 0);
1306 break;
1307 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop:
1308 rules[ALIGN_TOP] = a.getResourceId(attr, 0);
1309 break;
1310 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight:
1311 rules[ALIGN_RIGHT] = a.getResourceId(attr, 0);
1312 break;
1313 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom:
1314 rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0);
1315 break;
1316 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft:
1317 rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0;
1318 break;
1319 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop:
1320 rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0;
1321 break;
1322 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight:
1323 rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0;
1324 break;
1325 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom:
1326 rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0;
1327 break;
1328 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent:
1329 rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;
1330 break;
1331 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal:
1332 rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0;
1333 break;
1334 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical:
1335 rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;
1336 break;
1337 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf:
1338 rules[START_OF] = a.getResourceId(attr, 0);
1339 break;
1340 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf:
1341 rules[END_OF] = a.getResourceId(attr, 0);
1342 break;
1343 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart:
1344 rules[ALIGN_START] = a.getResourceId(attr, 0);
1345 break;
1346 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd:
1347 rules[ALIGN_END] = a.getResourceId(attr, 0);
1348 break;
1349 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart:
1350 rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0;
1351 break;
1352 case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd:
1353 rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0;
1354 break;
1355 }
1356 }
1357 mRulesChanged = true;
1358 System.arraycopy(rules, LEFT_OF, initialRules, LEFT_OF, VERB_COUNT);
1359
1360 a.recycle();
1361 }
1362
1363 public LayoutParams(int w, int h) {
1364 super(w, h);
1365 }
1366
1367 /**
1368 * {@inheritDoc}
1369 */
1370 public LayoutParams(ViewGroup.LayoutParams source) {
1371 super(source);
1372 }
1373
1374 /**
1375 * {@inheritDoc}
1376 */
1377 public LayoutParams(ViewGroup.MarginLayoutParams source) {
1378 super(source);
1379 }
1380
1381 /**
1382 * Copy constructor. Clones the width, height, margin values, and rules
1383 * of the source.
1384 *
1385 * @param source The layout params to copy from.
1386 */
1387 public LayoutParams(LayoutParams source) {
1388 super(source);
1389
1390 this.mIsRtlCompatibilityMode = source.mIsRtlCompatibilityMode;
1391 this.mRulesChanged = source.mRulesChanged;
1392 this.alignWithParent = source.alignWithParent;
1393
1394 System.arraycopy(source.mRules, LEFT_OF, this.mRules, LEFT_OF, VERB_COUNT);
1395 System.arraycopy(
1396 source.mInitialRules, LEFT_OF, this.mInitialRules, LEFT_OF, VERB_COUNT);
1397 }
1398
1399 @Override
1400 public String debug(String output) {
1401 return output + "ViewGroup.LayoutParams={ width=" + sizeToString(width) +
1402 ", height=" + sizeToString(height) + " }";
1403 }
1404
1405 /**
1406 * Adds a layout rule to be interpreted by the RelativeLayout.
1407 * <p>
1408 * This method should only be used for verbs that don't refer to a
1409 * sibling (ex. {@link #ALIGN_RIGHT}) or take a boolean
1410 * value ({@link #TRUE} for true or 0 for false). To
1411 * specify a verb that takes a subject, use {@link #addRule(int, int)}.
1412 * <p>
1413 * If the rule is relative to the layout direction (ex.
1414 * {@link #ALIGN_PARENT_START}), then the layout direction must be
1415 * resolved using {@link #resolveLayoutDirection(int)} before calling
1416 * {@link #getRule(int)} an absolute rule (ex.
1417 * {@link #ALIGN_PARENT_LEFT}.
1418 *
1419 * @param verb a layout verb, such as {@link #ALIGN_PARENT_LEFT}
1420 * @see #addRule(int, int)
1421 * @see #removeRule(int)
1422 * @see #getRule(int)
1423 */
1424 public void addRule(int verb) {
1425 addRule(verb, TRUE);
1426 }
1427
1428 /**
1429 * Adds a layout rule to be interpreted by the RelativeLayout.
1430 * <p>
1431 * Use this for verbs that refer to a sibling (ex.
1432 * {@link #ALIGN_RIGHT}) or take a boolean value (ex.
1433 * {@link #CENTER_IN_PARENT}).
1434 * <p>
1435 * If the rule is relative to the layout direction (ex.
1436 * {@link #START_OF}), then the layout direction must be resolved using
1437 * {@link #resolveLayoutDirection(int)} before calling
1438 * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}.
1439 *
1440 * @param verb a layout verb, such as {@link #ALIGN_RIGHT}
1441 * @param subject the ID of another view to use as an anchor, or a
1442 * boolean value (represented as {@link #TRUE} for true
1443 * or 0 for false)
1444 * @see #addRule(int)
1445 * @see #removeRule(int)
1446 * @see #getRule(int)
1447 */
1448 public void addRule(int verb, int subject) {
1449 // If we're removing a relative rule, we'll need to force layout
1450 // resolution the next time it's requested.
1451 if (!mNeedsLayoutResolution && isRelativeRule(verb)
1452 && mInitialRules[verb] != 0 && subject == 0) {
1453 mNeedsLayoutResolution = true;
1454 }
1455
1456 mRules[verb] = subject;
1457 mInitialRules[verb] = subject;
1458 mRulesChanged = true;
1459 }
1460
1461 /**
1462 * Removes a layout rule to be interpreted by the RelativeLayout.
1463 * <p>
1464 * If the rule is relative to the layout direction (ex.
1465 * {@link #START_OF}, {@link #ALIGN_PARENT_START}, etc.) then the
1466 * layout direction must be resolved using
1467 * {@link #resolveLayoutDirection(int)} before before calling
1468 * {@link #getRule(int)} with an absolute rule (ex. {@link #LEFT_OF}.
1469 *
1470 * @param verb One of the verbs defined by
1471 * {@link android.widget.RelativeLayout RelativeLayout}, such as
1472 * ALIGN_WITH_PARENT_LEFT.
1473 * @see #addRule(int)
1474 * @see #addRule(int, int)
1475 * @see #getRule(int)
1476 */
1477 public void removeRule(int verb) {
1478 addRule(verb, 0);
1479 }
1480
1481 /**
1482 * Returns the layout rule associated with a specific verb.
1483 *
1484 * @param verb one of the verbs defined by {@link RelativeLayout}, such
1485 * as ALIGN_WITH_PARENT_LEFT
1486 * @return the id of another view to use as an anchor, a boolean value
1487 * (represented as {@link RelativeLayout#TRUE} for true
1488 * or 0 for false), or -1 for verbs that don't refer to another
1489 * sibling (for example, ALIGN_WITH_PARENT_BOTTOM)
1490 * @see #addRule(int)
1491 * @see #addRule(int, int)
1492 */
1493 public int getRule(int verb) {
1494 return mRules[verb];
1495 }
1496
1497 private boolean hasRelativeRules() {
1498 return (mInitialRules[START_OF] != 0 || mInitialRules[END_OF] != 0 ||
1499 mInitialRules[ALIGN_START] != 0 || mInitialRules[ALIGN_END] != 0 ||
1500 mInitialRules[ALIGN_PARENT_START] != 0 || mInitialRules[ALIGN_PARENT_END] != 0);
1501 }
1502
1503 private boolean isRelativeRule(int rule) {
1504 return rule == START_OF || rule == END_OF
1505 || rule == ALIGN_START || rule == ALIGN_END
1506 || rule == ALIGN_PARENT_START || rule == ALIGN_PARENT_END;
1507 }
1508
1509 // The way we are resolving rules depends on the layout direction and if we are pre JB MR1
1510 // or not.
1511 //
1512 // If we are pre JB MR1 (said as "RTL compatibility mode"), "left"/"right" rules are having
1513 // predominance over any "start/end" rules that could have been defined. A special case:
1514 // if no "left"/"right" rule has been defined and "start"/"end" rules are defined then we
1515 // resolve those "start"/"end" rules to "left"/"right" respectively.
1516 //
1517 // If we are JB MR1+, then "start"/"end" rules are having predominance over "left"/"right"
1518 // rules. If no "start"/"end" rule is defined then we use "left"/"right" rules.
1519 //
1520 // In all cases, the result of the resolution should clear the "start"/"end" rules to leave
1521 // only the "left"/"right" rules at the end.
1522 private void resolveRules(int layoutDirection) {
1523 final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL);
1524
1525 // Reset to initial state
1526 System.arraycopy(mInitialRules, LEFT_OF, mRules, LEFT_OF, VERB_COUNT);
1527
1528 // Apply rules depending on direction and if we are in RTL compatibility mode
1529 if (mIsRtlCompatibilityMode) {
1530 if (mRules[ALIGN_START] != 0) {
1531 if (mRules[ALIGN_LEFT] == 0) {
1532 // "left" rule is not defined but "start" rule is: use the "start" rule as
1533 // the "left" rule
1534 mRules[ALIGN_LEFT] = mRules[ALIGN_START];
1535 }
1536 mRules[ALIGN_START] = 0;
1537 }
1538
1539 if (mRules[ALIGN_END] != 0) {
1540 if (mRules[ALIGN_RIGHT] == 0) {
1541 // "right" rule is not defined but "end" rule is: use the "end" rule as the
1542 // "right" rule
1543 mRules[ALIGN_RIGHT] = mRules[ALIGN_END];
1544 }
1545 mRules[ALIGN_END] = 0;
1546 }
1547
1548 if (mRules[START_OF] != 0) {
1549 if (mRules[LEFT_OF] == 0) {
1550 // "left" rule is not defined but "start" rule is: use the "start" rule as
1551 // the "left" rule
1552 mRules[LEFT_OF] = mRules[START_OF];
1553 }
1554 mRules[START_OF] = 0;
1555 }
1556
1557 if (mRules[END_OF] != 0) {
1558 if (mRules[RIGHT_OF] == 0) {
1559 // "right" rule is not defined but "end" rule is: use the "end" rule as the
1560 // "right" rule
1561 mRules[RIGHT_OF] = mRules[END_OF];
1562 }
1563 mRules[END_OF] = 0;
1564 }
1565
1566 if (mRules[ALIGN_PARENT_START] != 0) {
1567 if (mRules[ALIGN_PARENT_LEFT] == 0) {
1568 // "left" rule is not defined but "start" rule is: use the "start" rule as
1569 // the "left" rule
1570 mRules[ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START];
1571 }
1572 mRules[ALIGN_PARENT_START] = 0;
1573 }
1574
1575 if (mRules[ALIGN_PARENT_END] != 0) {
1576 if (mRules[ALIGN_PARENT_RIGHT] == 0) {
1577 // "right" rule is not defined but "end" rule is: use the "end" rule as the
1578 // "right" rule
1579 mRules[ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END];
1580 }
1581 mRules[ALIGN_PARENT_END] = 0;
1582 }
1583 } else {
1584 // JB MR1+ case
1585 if ((mRules[ALIGN_START] != 0 || mRules[ALIGN_END] != 0) &&
1586 (mRules[ALIGN_LEFT] != 0 || mRules[ALIGN_RIGHT] != 0)) {
1587 // "start"/"end" rules take precedence over "left"/"right" rules
1588 mRules[ALIGN_LEFT] = 0;
1589 mRules[ALIGN_RIGHT] = 0;
1590 }
1591 if (mRules[ALIGN_START] != 0) {
1592 // "start" rule resolved to "left" or "right" depending on the direction
1593 mRules[isLayoutRtl ? ALIGN_RIGHT : ALIGN_LEFT] = mRules[ALIGN_START];
1594 mRules[ALIGN_START] = 0;
1595 }
1596 if (mRules[ALIGN_END] != 0) {
1597 // "end" rule resolved to "left" or "right" depending on the direction
1598 mRules[isLayoutRtl ? ALIGN_LEFT : ALIGN_RIGHT] = mRules[ALIGN_END];
1599 mRules[ALIGN_END] = 0;
1600 }
1601
1602 if ((mRules[START_OF] != 0 || mRules[END_OF] != 0) &&
1603 (mRules[LEFT_OF] != 0 || mRules[RIGHT_OF] != 0)) {
1604 // "start"/"end" rules take precedence over "left"/"right" rules
1605 mRules[LEFT_OF] = 0;
1606 mRules[RIGHT_OF] = 0;
1607 }
1608 if (mRules[START_OF] != 0) {
1609 // "start" rule resolved to "left" or "right" depending on the direction
1610 mRules[isLayoutRtl ? RIGHT_OF : LEFT_OF] = mRules[START_OF];
1611 mRules[START_OF] = 0;
1612 }
1613 if (mRules[END_OF] != 0) {
1614 // "end" rule resolved to "left" or "right" depending on the direction
1615 mRules[isLayoutRtl ? LEFT_OF : RIGHT_OF] = mRules[END_OF];
1616 mRules[END_OF] = 0;
1617 }
1618
1619 if ((mRules[ALIGN_PARENT_START] != 0 || mRules[ALIGN_PARENT_END] != 0) &&
1620 (mRules[ALIGN_PARENT_LEFT] != 0 || mRules[ALIGN_PARENT_RIGHT] != 0)) {
1621 // "start"/"end" rules take precedence over "left"/"right" rules
1622 mRules[ALIGN_PARENT_LEFT] = 0;
1623 mRules[ALIGN_PARENT_RIGHT] = 0;
1624 }
1625 if (mRules[ALIGN_PARENT_START] != 0) {
1626 // "start" rule resolved to "left" or "right" depending on the direction
1627 mRules[isLayoutRtl ? ALIGN_PARENT_RIGHT : ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START];
1628 mRules[ALIGN_PARENT_START] = 0;
1629 }
1630 if (mRules[ALIGN_PARENT_END] != 0) {
1631 // "end" rule resolved to "left" or "right" depending on the direction
1632 mRules[isLayoutRtl ? ALIGN_PARENT_LEFT : ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END];
1633 mRules[ALIGN_PARENT_END] = 0;
1634 }
1635 }
1636
1637 mRulesChanged = false;
1638 mNeedsLayoutResolution = false;
1639 }
1640
1641 /**
1642 * Retrieves a complete list of all supported rules, where the index is the rule
1643 * verb, and the element value is the value specified, or "false" if it was never
1644 * set. If there are relative rules defined (*_START / *_END), they will be resolved
1645 * depending on the layout direction.
1646 *
1647 * @param layoutDirection the direction of the layout.
1648 * Should be either {@link View#LAYOUT_DIRECTION_LTR}
1649 * or {@link View#LAYOUT_DIRECTION_RTL}
1650 * @return the supported rules
1651 * @see #addRule(int, int)
1652 *
1653 * @hide
1654 */
1655 public int[] getRules(int layoutDirection) {
1656 resolveLayoutDirection(layoutDirection);
1657 return mRules;
1658 }
1659
1660 /**
1661 * Retrieves a complete list of all supported rules, where the index is the rule
1662 * verb, and the element value is the value specified, or "false" if it was never
1663 * set. There will be no resolution of relative rules done.
1664 *
1665 * @return the supported rules
1666 * @see #addRule(int, int)
1667 */
1668 public int[] getRules() {
1669 return mRules;
1670 }
1671
1672 /**
1673 * This will be called by {@link android.view.View#requestLayout()} to
1674 * resolve layout parameters that are relative to the layout direction.
1675 * <p>
1676 * After this method is called, any rules using layout-relative verbs
1677 * (ex. {@link #START_OF}) previously added via {@link #addRule(int)}
1678 * may only be accessed via their resolved absolute verbs (ex.
1679 * {@link #LEFT_OF}).
1680 */
1681 @Override
1682 public void resolveLayoutDirection(int layoutDirection) {
1683 if (shouldResolveLayoutDirection(layoutDirection)) {
1684 resolveRules(layoutDirection);
1685 }
1686
1687 // This will set the layout direction.
1688 super.resolveLayoutDirection(layoutDirection);
1689 }
1690
1691 private boolean shouldResolveLayoutDirection(int layoutDirection) {
1692 return (mNeedsLayoutResolution || hasRelativeRules())
1693 && (mRulesChanged || layoutDirection != getLayoutDirection());
1694 }
1695
1696 /** @hide */
1697 @Override
1698 protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
1699 super.encodeProperties(encoder);
1700 encoder.addProperty("layout:alignWithParent", alignWithParent);
1701 }
1702 }
1703
1704 private static class DependencyGraph {
1705 /**
1706 * List of all views in the graph.
1707 */
1708 private ArrayList<Node> mNodes = new ArrayList<Node>();
1709
1710 /**
1711 * List of nodes in the graph. Each node is identified by its
1712 * view id (see View#getId()).
1713 */
1714 private SparseArray<Node> mKeyNodes = new SparseArray<Node>();
1715
1716 /**
1717 * Temporary data structure used to build the list of roots
1718 * for this graph.
1719 */
1720 private ArrayDeque<Node> mRoots = new ArrayDeque<Node>();
1721
1722 /**
1723 * Clears the graph.
1724 */
1725 void clear() {
1726 final ArrayList<Node> nodes = mNodes;
1727 final int count = nodes.size();
1728
1729 for (int i = 0; i < count; i++) {
1730 nodes.get(i).release();
1731 }
1732 nodes.clear();
1733
1734 mKeyNodes.clear();
1735 mRoots.clear();
1736 }
1737
1738 /**
1739 * Adds a view to the graph.
1740 *
1741 * @param view The view to be added as a node to the graph.
1742 */
1743 void add(View view) {
1744 final int id = view.getId();
1745 final Node node = Node.acquire(view);
1746
1747 if (id != View.NO_ID) {
1748 mKeyNodes.put(id, node);
1749 }
1750
1751 mNodes.add(node);
1752 }
1753
1754 /**
1755 * Builds a sorted list of views. The sorting order depends on the dependencies
1756 * between the view. For instance, if view C needs view A to be processed first
1757 * and view A needs view B to be processed first, the dependency graph
1758 * is: B -> A -> C. The sorted array will contain views B, A and C in this order.
1759 *
1760 * @param sorted The sorted list of views. The length of this array must
1761 * be equal to getChildCount().
1762 * @param rules The list of rules to take into account.
1763 */
1764 void getSortedViews(View[] sorted, int... rules) {
1765 final ArrayDeque<Node> roots = findRoots(rules);
1766 int index = 0;
1767
1768 Node node;
1769 while ((node = roots.pollLast()) != null) {
1770 final View view = node.view;
1771 final int key = view.getId();
1772
1773 sorted[index++] = view;
1774
1775 final ArrayMap<Node, DependencyGraph> dependents = node.dependents;
1776 final int count = dependents.size();
1777 for (int i = 0; i < count; i++) {
1778 final Node dependent = dependents.keyAt(i);
1779 final SparseArray<Node> dependencies = dependent.dependencies;
1780
1781 dependencies.remove(key);
1782 if (dependencies.size() == 0) {
1783 roots.add(dependent);
1784 }
1785 }
1786 }
1787
1788 if (index < sorted.length) {
1789 throw new IllegalStateException("Circular dependencies cannot exist"
1790 + " in RelativeLayout");
1791 }
1792 }
1793
1794 /**
1795 * Finds the roots of the graph. A root is a node with no dependency and
1796 * with [0..n] dependents.
1797 *
1798 * @param rulesFilter The list of rules to consider when building the
1799 * dependencies
1800 *
1801 * @return A list of node, each being a root of the graph
1802 */
1803 private ArrayDeque<Node> findRoots(int[] rulesFilter) {
1804 final SparseArray<Node> keyNodes = mKeyNodes;
1805 final ArrayList<Node> nodes = mNodes;
1806 final int count = nodes.size();
1807
1808 // Find roots can be invoked several times, so make sure to clear
1809 // all dependents and dependencies before running the algorithm
1810 for (int i = 0; i < count; i++) {
1811 final Node node = nodes.get(i);
1812 node.dependents.clear();
1813 node.dependencies.clear();
1814 }
1815
1816 // Builds up the dependents and dependencies for each node of the graph
1817 for (int i = 0; i < count; i++) {
1818 final Node node = nodes.get(i);
1819
1820 final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams();
1821 final int[] rules = layoutParams.mRules;
1822 final int rulesCount = rulesFilter.length;
1823
1824 // Look only the the rules passed in parameter, this way we build only the
1825 // dependencies for a specific set of rules
1826 for (int j = 0; j < rulesCount; j++) {
1827 final int rule = rules[rulesFilter[j]];
1828 if (rule > 0) {
1829 // The node this node depends on
1830 final Node dependency = keyNodes.get(rule);
1831 // Skip unknowns and self dependencies
1832 if (dependency == null || dependency == node) {
1833 continue;
1834 }
1835 // Add the current node as a dependent
1836 dependency.dependents.put(node, this);
1837 // Add a dependency to the current node
1838 node.dependencies.put(rule, dependency);
1839 }
1840 }
1841 }
1842
1843 final ArrayDeque<Node> roots = mRoots;
1844 roots.clear();
1845
1846 // Finds all the roots in the graph: all nodes with no dependencies
1847 for (int i = 0; i < count; i++) {
1848 final Node node = nodes.get(i);
1849 if (node.dependencies.size() == 0) roots.addLast(node);
1850 }
1851
1852 return roots;
1853 }
1854
1855 /**
1856 * A node in the dependency graph. A node is a view, its list of dependencies
1857 * and its list of dependents.
1858 *
1859 * A node with no dependent is considered a root of the graph.
1860 */
1861 static class Node {
1862 /**
1863 * The view representing this node in the layout.
1864 */
1865 View view;
1866
1867 /**
1868 * The list of dependents for this node; a dependent is a node
1869 * that needs this node to be processed first.
1870 */
1871 final ArrayMap<Node, DependencyGraph> dependents =
1872 new ArrayMap<Node, DependencyGraph>();
1873
1874 /**
1875 * The list of dependencies for this node.
1876 */
1877 final SparseArray<Node> dependencies = new SparseArray<Node>();
1878
1879 /*
1880 * START POOL IMPLEMENTATION
1881 */
1882 // The pool is static, so all nodes instances are shared across
1883 // activities, that's why we give it a rather high limit
1884 private static final int POOL_LIMIT = 100;
1885 private static final SynchronizedPool<Node> sPool =
1886 new SynchronizedPool<Node>(POOL_LIMIT);
1887
1888 static Node acquire(View view) {
1889 Node node = sPool.acquire();
1890 if (node == null) {
1891 node = new Node();
1892 }
1893 node.view = view;
1894 return node;
1895 }
1896
1897 void release() {
1898 view = null;
1899 dependents.clear();
1900 dependencies.clear();
1901
1902 sPool.release(this);
1903 }
1904 /*
1905 * END POOL IMPLEMENTATION
1906 */
1907 }
1908 }
1909}