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