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