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