RelativeLayout.java revision 980a938c1c9a6a5791a8240e5a1e6638ab28dc77
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    @Override
295    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
296        if (mDirtyHierarchy) {
297            mDirtyHierarchy = false;
298            sortChildren();
299        }
300
301        int myWidth = -1;
302        int myHeight = -1;
303
304        int width = 0;
305        int height = 0;
306
307        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
308        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
309        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
310        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
311
312        // Record our dimensions if they are known;
313        if (widthMode != MeasureSpec.UNSPECIFIED) {
314            myWidth = widthSize;
315        }
316
317        if (heightMode != MeasureSpec.UNSPECIFIED) {
318            myHeight = heightSize;
319        }
320
321        if (widthMode == MeasureSpec.EXACTLY) {
322            width = myWidth;
323        }
324
325        if (heightMode == MeasureSpec.EXACTLY) {
326            height = myHeight;
327        }
328
329        mHasBaselineAlignedChild = false;
330
331        View ignore = null;
332        int gravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
333        final boolean horizontalGravity = gravity != Gravity.LEFT && gravity != 0;
334        gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
335        final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;
336
337        int left = Integer.MAX_VALUE;
338        int top = Integer.MAX_VALUE;
339        int right = Integer.MIN_VALUE;
340        int bottom = Integer.MIN_VALUE;
341
342        boolean offsetHorizontalAxis = false;
343        boolean offsetVerticalAxis = false;
344
345        if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
346            ignore = findViewById(mIgnoreGravity);
347        }
348
349        final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
350        final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;
351
352        View[] views = mSortedHorizontalChildren;
353        int count = views.length;
354        for (int i = 0; i < count; i++) {
355            View child = views[i];
356            if (child.getVisibility() != GONE) {
357                LayoutParams params = (LayoutParams) child.getLayoutParams();
358
359                applyHorizontalSizeRules(params, myWidth);
360                measureChildHorizontal(child, params, myWidth, myHeight);
361                if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
362                    offsetHorizontalAxis = true;
363                }
364            }
365        }
366
367        views = mSortedVerticalChildren;
368        count = views.length;
369
370        for (int i = 0; i < count; i++) {
371            View child = views[i];
372            if (child.getVisibility() != GONE) {
373                LayoutParams params = (LayoutParams) child.getLayoutParams();
374
375                applyVerticalSizeRules(params, myHeight);
376                measureChild(child, params, myWidth, myHeight);
377                if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
378                    offsetVerticalAxis = true;
379                }
380
381                if (isWrapContentWidth) {
382                    width = Math.max(width, params.mRight);
383                }
384
385                if (isWrapContentHeight) {
386                    height = Math.max(height, params.mBottom);
387                }
388
389                if (child != ignore || verticalGravity) {
390                    left = Math.min(left, params.mLeft - params.leftMargin);
391                    top = Math.min(top, params.mTop - params.topMargin);
392                }
393
394                if (child != ignore || horizontalGravity) {
395                    right = Math.max(right, params.mRight + params.rightMargin);
396                    bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
397                }
398            }
399        }
400
401        if (mHasBaselineAlignedChild) {
402            for (int i = 0; i < count; i++) {
403                View child = getChildAt(i);
404                if (child.getVisibility() != GONE) {
405                    LayoutParams params = (LayoutParams) child.getLayoutParams();
406                    alignBaseline(child, params);
407
408                    if (child != ignore || verticalGravity) {
409                        left = Math.min(left, params.mLeft - params.leftMargin);
410                        top = Math.min(top, params.mTop - params.topMargin);
411                    }
412
413                    if (child != ignore || horizontalGravity) {
414                        right = Math.max(right, params.mRight + params.rightMargin);
415                        bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
416                    }
417                }
418            }
419        }
420
421        if (isWrapContentWidth) {
422            // Width already has left padding in it since it was calculated by looking at
423            // the right of each child view
424            width += mPaddingRight;
425
426            if (mLayoutParams.width >= 0) {
427                width = Math.max(width, mLayoutParams.width);
428            }
429
430            width = Math.max(width, getSuggestedMinimumWidth());
431            width = resolveSize(width, widthMeasureSpec);
432
433            if (offsetHorizontalAxis) {
434                for (int i = 0; i < count; i++) {
435                    View child = getChildAt(i);
436                    if (child.getVisibility() != GONE) {
437                        LayoutParams params = (LayoutParams) child.getLayoutParams();
438                        final int[] rules = params.getRules();
439                        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
440                            centerHorizontal(child, params, width);
441                        }
442                    }
443                }
444            }
445        }
446
447        if (isWrapContentHeight) {
448            // Height already has top padding in it since it was calculated by looking at
449            // the bottom of each child view
450            height += mPaddingBottom;
451
452            if (mLayoutParams.height >= 0) {
453                height = Math.max(height, mLayoutParams.height);
454            }
455
456            height = Math.max(height, getSuggestedMinimumHeight());
457            height = resolveSize(height, heightMeasureSpec);
458
459            if (offsetVerticalAxis) {
460                for (int i = 0; i < count; i++) {
461                    View child = getChildAt(i);
462                    if (child.getVisibility() != GONE) {
463                        LayoutParams params = (LayoutParams) child.getLayoutParams();
464                        final int[] rules = params.getRules();
465                        if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
466                            centerVertical(child, params, height);
467                        }
468                    }
469                }
470            }
471        }
472
473        if (horizontalGravity || verticalGravity) {
474            final Rect selfBounds = mSelfBounds;
475            selfBounds.set(mPaddingLeft, mPaddingTop, width - mPaddingRight,
476                    height - mPaddingBottom);
477
478            final Rect contentBounds = mContentBounds;
479            Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds);
480
481            final int horizontalOffset = contentBounds.left - left;
482            final int verticalOffset = contentBounds.top - top;
483            if (horizontalOffset != 0 || verticalOffset != 0) {
484                for (int i = 0; i < count; i++) {
485                    View child = getChildAt(i);
486                    if (child.getVisibility() != GONE && child != ignore) {
487                        LayoutParams params = (LayoutParams) child.getLayoutParams();
488                        if (horizontalGravity) {
489                            params.mLeft += horizontalOffset;
490                            params.mRight += horizontalOffset;
491                        }
492                        if (verticalGravity) {
493                            params.mTop += verticalOffset;
494                            params.mBottom += verticalOffset;
495                        }
496                    }
497                }
498            }
499        }
500
501        setMeasuredDimension(width, height);
502    }
503
504    private void alignBaseline(View child, LayoutParams params) {
505        int[] rules = params.getRules();
506        int anchorBaseline = getRelatedViewBaseline(rules, ALIGN_BASELINE);
507
508        if (anchorBaseline != -1) {
509            LayoutParams anchorParams = getRelatedViewParams(rules, ALIGN_BASELINE);
510            if (anchorParams != null) {
511                int offset = anchorParams.mTop + anchorBaseline;
512                int baseline = child.getBaseline();
513                if (baseline != -1) {
514                    offset -= baseline;
515                }
516                int height = params.mBottom - params.mTop;
517                params.mTop = offset;
518                params.mBottom = params.mTop + height;
519            }
520        }
521
522        if (mBaselineView == null) {
523            mBaselineView = child;
524        } else {
525            LayoutParams lp = (LayoutParams) mBaselineView.getLayoutParams();
526            if (params.mTop < lp.mTop || (params.mTop == lp.mTop && params.mLeft < lp.mLeft)) {
527                mBaselineView = child;
528            }
529        }
530    }
531
532    /**
533     * Measure a child. The child should have left, top, right and bottom information
534     * stored in its LayoutParams. If any of these values is -1 it means that the view
535     * can extend up to the corresponding edge.
536     *
537     * @param child Child to measure
538     * @param params LayoutParams associated with child
539     * @param myWidth Width of the the RelativeLayout
540     * @param myHeight Height of the RelativeLayout
541     */
542    private void measureChild(View child, LayoutParams params, int myWidth, int myHeight) {
543        int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft,
544                params.mRight, params.width,
545                params.leftMargin, params.rightMargin,
546                mPaddingLeft, mPaddingRight,
547                myWidth);
548        int childHeightMeasureSpec = getChildMeasureSpec(params.mTop,
549                params.mBottom, params.height,
550                params.topMargin, params.bottomMargin,
551                mPaddingTop, mPaddingBottom,
552                myHeight);
553        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
554    }
555
556    private void measureChildHorizontal(View child, LayoutParams params, int myWidth, int myHeight) {
557        int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft,
558                params.mRight, params.width,
559                params.leftMargin, params.rightMargin,
560                mPaddingLeft, mPaddingRight,
561                myWidth);
562        int childHeightMeasureSpec;
563        if (params.width == LayoutParams.MATCH_PARENT) {
564            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
565        } else {
566            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
567        }
568        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
569    }
570
571    /**
572     * Get a measure spec that accounts for all of the constraints on this view.
573     * This includes size contstraints imposed by the RelativeLayout as well as
574     * the View's desired dimension.
575     *
576     * @param childStart The left or top field of the child's layout params
577     * @param childEnd The right or bottom field of the child's layout params
578     * @param childSize The child's desired size (the width or height field of
579     *        the child's layout params)
580     * @param startMargin The left or top margin
581     * @param endMargin The right or bottom margin
582     * @param startPadding mPaddingLeft or mPaddingTop
583     * @param endPadding mPaddingRight or mPaddingBottom
584     * @param mySize The width or height of this view (the RelativeLayout)
585     * @return MeasureSpec for the child
586     */
587    private int getChildMeasureSpec(int childStart, int childEnd,
588            int childSize, int startMargin, int endMargin, int startPadding,
589            int endPadding, int mySize) {
590        int childSpecMode = 0;
591        int childSpecSize = 0;
592
593        // Figure out start and end bounds.
594        int tempStart = childStart;
595        int tempEnd = childEnd;
596
597        // If the view did not express a layout constraint for an edge, use
598        // view's margins and our padding
599        if (tempStart < 0) {
600            tempStart = startPadding + startMargin;
601        }
602        if (tempEnd < 0) {
603            tempEnd = mySize - endPadding - endMargin;
604        }
605
606        // Figure out maximum size available to this view
607        int maxAvailable = tempEnd - tempStart;
608
609        if (childStart >= 0 && childEnd >= 0) {
610            // Constraints fixed both edges, so child must be an exact size
611            childSpecMode = MeasureSpec.EXACTLY;
612            childSpecSize = maxAvailable;
613        } else {
614            if (childSize >= 0) {
615                // Child wanted an exact size. Give as much as possible
616                childSpecMode = MeasureSpec.EXACTLY;
617
618                if (maxAvailable >= 0) {
619                    // We have a maxmum size in this dimension.
620                    childSpecSize = Math.min(maxAvailable, childSize);
621                } else {
622                    // We can grow in this dimension.
623                    childSpecSize = childSize;
624                }
625            } else if (childSize == LayoutParams.MATCH_PARENT) {
626                // Child wanted to be as big as possible. Give all availble
627                // space
628                childSpecMode = MeasureSpec.EXACTLY;
629                childSpecSize = maxAvailable;
630            } else if (childSize == LayoutParams.WRAP_CONTENT) {
631                // Child wants to wrap content. Use AT_MOST
632                // to communicate available space if we know
633                // our max size
634                if (maxAvailable >= 0) {
635                    // We have a maxmum size in this dimension.
636                    childSpecMode = MeasureSpec.AT_MOST;
637                    childSpecSize = maxAvailable;
638                } else {
639                    // We can grow in this dimension. Child can be as big as it
640                    // wants
641                    childSpecMode = MeasureSpec.UNSPECIFIED;
642                    childSpecSize = 0;
643                }
644            }
645        }
646
647        return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
648    }
649
650    private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth,
651            boolean wrapContent) {
652
653        int[] rules = params.getRules();
654
655        if (params.mLeft < 0 && params.mRight >= 0) {
656            // Right is fixed, but left varies
657            params.mLeft = params.mRight - child.getMeasuredWidth();
658        } else if (params.mLeft >= 0 && params.mRight < 0) {
659            // Left is fixed, but right varies
660            params.mRight = params.mLeft + child.getMeasuredWidth();
661        } else if (params.mLeft < 0 && params.mRight < 0) {
662            // Both left and right vary
663            if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
664                if (!wrapContent) {
665                    centerHorizontal(child, params, myWidth);
666                } else {
667                    params.mLeft = mPaddingLeft + params.leftMargin;
668                    params.mRight = params.mLeft + child.getMeasuredWidth();
669                }
670                return true;
671            } else {
672                params.mLeft = mPaddingLeft + params.leftMargin;
673                params.mRight = params.mLeft + child.getMeasuredWidth();
674            }
675        }
676        return false;
677    }
678
679    private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
680            boolean wrapContent) {
681
682        int[] rules = params.getRules();
683
684        if (params.mTop < 0 && params.mBottom >= 0) {
685            // Bottom is fixed, but top varies
686            params.mTop = params.mBottom - child.getMeasuredHeight();
687        } else if (params.mTop >= 0 && params.mBottom < 0) {
688            // Top is fixed, but bottom varies
689            params.mBottom = params.mTop + child.getMeasuredHeight();
690        } else if (params.mTop < 0 && params.mBottom < 0) {
691            // Both top and bottom vary
692            if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
693                if (!wrapContent) {
694                    centerVertical(child, params, myHeight);
695                } else {
696                    params.mTop = mPaddingTop + params.topMargin;
697                    params.mBottom = params.mTop + child.getMeasuredHeight();
698                }
699                return true;
700            } else {
701                params.mTop = mPaddingTop + params.topMargin;
702                params.mBottom = params.mTop + child.getMeasuredHeight();
703            }
704        }
705        return false;
706    }
707
708    private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) {
709        int[] rules = childParams.getRules();
710        RelativeLayout.LayoutParams anchorParams;
711
712        // -1 indicated a "soft requirement" in that direction. For example:
713        // left=10, right=-1 means the view must start at 10, but can go as far as it wants to the right
714        // left =-1, right=10 means the view must end at 10, but can go as far as it wants to the left
715        // left=10, right=20 means the left and right ends are both fixed
716        childParams.mLeft = -1;
717        childParams.mRight = -1;
718
719        anchorParams = getRelatedViewParams(rules, LEFT_OF);
720        if (anchorParams != null) {
721            childParams.mRight = anchorParams.mLeft - (anchorParams.leftMargin +
722                    childParams.rightMargin);
723        } else if (childParams.alignWithParent && rules[LEFT_OF] != 0) {
724            if (myWidth >= 0) {
725                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
726            } else {
727                // FIXME uh oh...
728            }
729        }
730
731        anchorParams = getRelatedViewParams(rules, RIGHT_OF);
732        if (anchorParams != null) {
733            childParams.mLeft = anchorParams.mRight + (anchorParams.rightMargin +
734                    childParams.leftMargin);
735        } else if (childParams.alignWithParent && rules[RIGHT_OF] != 0) {
736            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
737        }
738
739        anchorParams = getRelatedViewParams(rules, ALIGN_LEFT);
740        if (anchorParams != null) {
741            childParams.mLeft = anchorParams.mLeft + childParams.leftMargin;
742        } else if (childParams.alignWithParent && rules[ALIGN_LEFT] != 0) {
743            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
744        }
745
746        anchorParams = getRelatedViewParams(rules, ALIGN_RIGHT);
747        if (anchorParams != null) {
748            childParams.mRight = anchorParams.mRight - childParams.rightMargin;
749        } else if (childParams.alignWithParent && rules[ALIGN_RIGHT] != 0) {
750            if (myWidth >= 0) {
751                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
752            } else {
753                // FIXME uh oh...
754            }
755        }
756
757        if (0 != rules[ALIGN_PARENT_LEFT]) {
758            childParams.mLeft = mPaddingLeft + childParams.leftMargin;
759        }
760
761        if (0 != rules[ALIGN_PARENT_RIGHT]) {
762            if (myWidth >= 0) {
763                childParams.mRight = myWidth - mPaddingRight - childParams.rightMargin;
764            } else {
765                // FIXME uh oh...
766            }
767        }
768    }
769
770    private void applyVerticalSizeRules(LayoutParams childParams, int myHeight) {
771        int[] rules = childParams.getRules();
772        RelativeLayout.LayoutParams anchorParams;
773
774        childParams.mTop = -1;
775        childParams.mBottom = -1;
776
777        anchorParams = getRelatedViewParams(rules, ABOVE);
778        if (anchorParams != null) {
779            childParams.mBottom = anchorParams.mTop - (anchorParams.topMargin +
780                    childParams.bottomMargin);
781        } else if (childParams.alignWithParent && rules[ABOVE] != 0) {
782            if (myHeight >= 0) {
783                childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
784            } else {
785                // FIXME uh oh...
786            }
787        }
788
789        anchorParams = getRelatedViewParams(rules, BELOW);
790        if (anchorParams != null) {
791            childParams.mTop = anchorParams.mBottom + (anchorParams.bottomMargin +
792                    childParams.topMargin);
793        } else if (childParams.alignWithParent && rules[BELOW] != 0) {
794            childParams.mTop = mPaddingTop + childParams.topMargin;
795        }
796
797        anchorParams = getRelatedViewParams(rules, ALIGN_TOP);
798        if (anchorParams != null) {
799            childParams.mTop = anchorParams.mTop + childParams.topMargin;
800        } else if (childParams.alignWithParent && rules[ALIGN_TOP] != 0) {
801            childParams.mTop = mPaddingTop + childParams.topMargin;
802        }
803
804        anchorParams = getRelatedViewParams(rules, ALIGN_BOTTOM);
805        if (anchorParams != null) {
806            childParams.mBottom = anchorParams.mBottom - childParams.bottomMargin;
807        } else if (childParams.alignWithParent && rules[ALIGN_BOTTOM] != 0) {
808            if (myHeight >= 0) {
809                childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
810            } else {
811                // FIXME uh oh...
812            }
813        }
814
815        if (0 != rules[ALIGN_PARENT_TOP]) {
816            childParams.mTop = mPaddingTop + childParams.topMargin;
817        }
818
819        if (0 != rules[ALIGN_PARENT_BOTTOM]) {
820            if (myHeight >= 0) {
821                childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
822            } else {
823                // FIXME uh oh...
824            }
825        }
826
827        if (rules[ALIGN_BASELINE] != 0) {
828            mHasBaselineAlignedChild = true;
829        }
830    }
831
832    private View getRelatedView(int[] rules, int relation) {
833        int id = rules[relation];
834        if (id != 0) {
835            DependencyGraph.Node node = mGraph.mKeyNodes.get(id);
836            if (node == null) return null;
837            View v = node.view;
838
839            // Find the first non-GONE view up the chain
840            while (v.getVisibility() == View.GONE) {
841                rules = ((LayoutParams) v.getLayoutParams()).getRules();
842                node = mGraph.mKeyNodes.get((rules[relation]));
843                if (node == null) return null;
844                v = node.view;
845            }
846
847            return v;
848        }
849
850        return null;
851    }
852
853    private LayoutParams getRelatedViewParams(int[] rules, int relation) {
854        View v = getRelatedView(rules, relation);
855        if (v != null) {
856            ViewGroup.LayoutParams params = v.getLayoutParams();
857            if (params instanceof LayoutParams) {
858                return (LayoutParams) v.getLayoutParams();
859            }
860        }
861        return null;
862    }
863
864    private int getRelatedViewBaseline(int[] rules, int relation) {
865        View v = getRelatedView(rules, relation);
866        if (v != null) {
867            return v.getBaseline();
868        }
869        return -1;
870    }
871
872    private void centerHorizontal(View child, LayoutParams params, int myWidth) {
873        int childWidth = child.getMeasuredWidth();
874        int left = (myWidth - childWidth) / 2;
875
876        params.mLeft = left;
877        params.mRight = left + childWidth;
878    }
879
880    private void centerVertical(View child, LayoutParams params, int myHeight) {
881        int childHeight = child.getMeasuredHeight();
882        int top = (myHeight - childHeight) / 2;
883
884        params.mTop = top;
885        params.mBottom = top + childHeight;
886    }
887
888    @Override
889    protected void onLayout(boolean changed, int l, int t, int r, int b) {
890        //  The layout has actually already been performed and the positions
891        //  cached.  Apply the cached values to the children.
892        int count = getChildCount();
893
894        for (int i = 0; i < count; i++) {
895            View child = getChildAt(i);
896            if (child.getVisibility() != GONE) {
897                RelativeLayout.LayoutParams st =
898                        (RelativeLayout.LayoutParams) child.getLayoutParams();
899                child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
900
901            }
902        }
903    }
904
905    @Override
906    public LayoutParams generateLayoutParams(AttributeSet attrs) {
907        return new RelativeLayout.LayoutParams(getContext(), attrs);
908    }
909
910    /**
911     * Returns a set of layout parameters with a width of
912     * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT},
913     * a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
914     */
915    @Override
916    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
917        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
918    }
919
920    // Override to allow type-checking of LayoutParams.
921    @Override
922    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
923        return p instanceof RelativeLayout.LayoutParams;
924    }
925
926    @Override
927    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
928        return new LayoutParams(p);
929    }
930
931    @Override
932    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
933        if (mTopToBottomLeftToRightSet == null) {
934            mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator());
935        }
936
937        // sort children top-to-bottom and left-to-right
938        for (int i = 0, count = getChildCount(); i < count; i++) {
939            mTopToBottomLeftToRightSet.add(getChildAt(i));
940        }
941
942        for (View view : mTopToBottomLeftToRightSet) {
943            if (view.dispatchPopulateAccessibilityEvent(event)) {
944                mTopToBottomLeftToRightSet.clear();
945                return true;
946            }
947        }
948
949        mTopToBottomLeftToRightSet.clear();
950        return false;
951    }
952
953    /**
954     * Compares two views in left-to-right and top-to-bottom fashion.
955     */
956     private class TopToBottomLeftToRightComparator implements Comparator<View> {
957        public int compare(View first, View second) {
958            // top - bottom
959            int topDifference = first.getTop() - second.getTop();
960            if (topDifference != 0) {
961                return topDifference;
962            }
963            // left - right
964            int leftDifference = first.getLeft() - second.getLeft();
965            if (leftDifference != 0) {
966                return leftDifference;
967            }
968            // break tie by height
969            int heightDiference = first.getHeight() - second.getHeight();
970            if (heightDiference != 0) {
971                return heightDiference;
972            }
973            // break tie by width
974            int widthDiference = first.getWidth() - second.getWidth();
975            if (widthDiference != 0) {
976                return widthDiference;
977            }
978            return 0;
979        }
980    }
981
982    /**
983     * Per-child layout information associated with RelativeLayout.
984     *
985     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignWithParentIfMissing
986     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toLeftOf
987     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toRightOf
988     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_above
989     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_below
990     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBaseline
991     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignLeft
992     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignTop
993     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignRight
994     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignBottom
995     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentLeft
996     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentTop
997     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentRight
998     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentBottom
999     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent
1000     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal
1001     * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical
1002     */
1003    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1004        @ViewDebug.ExportedProperty(resolveId = true, indexMapping = {
1005            @ViewDebug.IntToString(from = ABOVE,               to = "above"),
1006            @ViewDebug.IntToString(from = ALIGN_BASELINE,      to = "alignBaseline"),
1007            @ViewDebug.IntToString(from = ALIGN_BOTTOM,        to = "alignBottom"),
1008            @ViewDebug.IntToString(from = ALIGN_LEFT,          to = "alignLeft"),
1009            @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"),
1010            @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT,   to = "alignParentLeft"),
1011            @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT,  to = "alignParentRight"),
1012            @ViewDebug.IntToString(from = ALIGN_PARENT_TOP,    to = "alignParentTop"),
1013            @ViewDebug.IntToString(from = ALIGN_RIGHT,         to = "alignRight"),
1014            @ViewDebug.IntToString(from = ALIGN_TOP,           to = "alignTop"),
1015            @ViewDebug.IntToString(from = BELOW,               to = "below"),
1016            @ViewDebug.IntToString(from = CENTER_HORIZONTAL,   to = "centerHorizontal"),
1017            @ViewDebug.IntToString(from = CENTER_IN_PARENT,    to = "center"),
1018            @ViewDebug.IntToString(from = CENTER_VERTICAL,     to = "centerVertical"),
1019            @ViewDebug.IntToString(from = LEFT_OF,             to = "leftOf"),
1020            @ViewDebug.IntToString(from = RIGHT_OF,            to = "rightOf")
1021        }, mapping = {
1022            @ViewDebug.IntToString(from = TRUE, to = "true"),
1023            @ViewDebug.IntToString(from = 0,    to = "false/NO_ID")
1024        })
1025        private int[] mRules = new int[VERB_COUNT];
1026
1027        private int mLeft, mTop, mRight, mBottom;
1028
1029        /**
1030         * When true, uses the parent as the anchor if the anchor doesn't exist or if
1031         * the anchor's visibility is GONE.
1032         */
1033        @ViewDebug.ExportedProperty
1034        public boolean alignWithParent;
1035
1036        public LayoutParams(Context c, AttributeSet attrs) {
1037            super(c, attrs);
1038
1039            TypedArray a = c.obtainStyledAttributes(attrs,
1040                    com.android.internal.R.styleable.RelativeLayout_Layout);
1041
1042            final int[] rules = mRules;
1043
1044            final int N = a.getIndexCount();
1045            for (int i = 0; i < N; i++) {
1046                int attr = a.getIndex(i);
1047                switch (attr) {
1048                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignWithParentIfMissing:
1049                        alignWithParent = a.getBoolean(attr, false);
1050                        break;
1051                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toLeftOf:
1052                        rules[LEFT_OF] = a.getResourceId(attr, 0);
1053                        break;
1054                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toRightOf:
1055                        rules[RIGHT_OF] = a.getResourceId(attr, 0);
1056                        break;
1057                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_above:
1058                        rules[ABOVE] = a.getResourceId(attr, 0);
1059                        break;
1060                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_below:
1061                        rules[BELOW] = a.getResourceId(attr, 0);
1062                        break;
1063                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBaseline:
1064                        rules[ALIGN_BASELINE] = a.getResourceId(attr, 0);
1065                        break;
1066                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignLeft:
1067                        rules[ALIGN_LEFT] = a.getResourceId(attr, 0);
1068                        break;
1069                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignTop:
1070                        rules[ALIGN_TOP] = a.getResourceId(attr, 0);
1071                        break;
1072                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignRight:
1073                        rules[ALIGN_RIGHT] = a.getResourceId(attr, 0);
1074                        break;
1075                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignBottom:
1076                        rules[ALIGN_BOTTOM] = a.getResourceId(attr, 0);
1077                        break;
1078                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentLeft:
1079                        rules[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0;
1080                        break;
1081                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentTop:
1082                        rules[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0;
1083                        break;
1084                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentRight:
1085                        rules[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0;
1086                        break;
1087                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentBottom:
1088                        rules[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0;
1089                        break;
1090                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerInParent:
1091                        rules[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;
1092                        break;
1093                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerHorizontal:
1094                        rules[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0;
1095                        break;
1096                    case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical:
1097                        rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;
1098                       break;
1099                }
1100            }
1101
1102            a.recycle();
1103        }
1104
1105        public LayoutParams(int w, int h) {
1106            super(w, h);
1107        }
1108
1109        /**
1110         * {@inheritDoc}
1111         */
1112        public LayoutParams(ViewGroup.LayoutParams source) {
1113            super(source);
1114        }
1115
1116        /**
1117         * {@inheritDoc}
1118         */
1119        public LayoutParams(ViewGroup.MarginLayoutParams source) {
1120            super(source);
1121        }
1122
1123        @Override
1124        public String debug(String output) {
1125            return output + "ViewGroup.LayoutParams={ width=" + sizeToString(width) +
1126                    ", height=" + sizeToString(height) + " }";
1127        }
1128
1129        /**
1130         * Adds a layout rule to be interpreted by the RelativeLayout. This
1131         * method should only be used for constraints that don't refer to another sibling
1132         * (e.g., CENTER_IN_PARENT) or take a boolean value ({@link RelativeLayout#TRUE}
1133         * for true or - for false). To specify a verb that takes a subject, use
1134         * {@link #addRule(int, int)} instead.
1135         *
1136         * @param verb One of the verbs defined by
1137         *        {@link android.widget.RelativeLayout RelativeLayout}, such as
1138         *        ALIGN_WITH_PARENT_LEFT.
1139         * @see #addRule(int, int)
1140         */
1141        public void addRule(int verb) {
1142            mRules[verb] = TRUE;
1143        }
1144
1145        /**
1146         * Adds a layout rule to be interpreted by the RelativeLayout. Use this for
1147         * verbs that take a target, such as a sibling (ALIGN_RIGHT) or a boolean
1148         * value (VISIBLE).
1149         *
1150         * @param verb One of the verbs defined by
1151         *        {@link android.widget.RelativeLayout RelativeLayout}, such as
1152         *         ALIGN_WITH_PARENT_LEFT.
1153         * @param anchor The id of another view to use as an anchor,
1154         *        or a boolean value(represented as {@link RelativeLayout#TRUE})
1155         *        for true or 0 for false).  For verbs that don't refer to another sibling
1156         *        (for example, ALIGN_WITH_PARENT_BOTTOM) just use -1.
1157         * @see #addRule(int)
1158         */
1159        public void addRule(int verb, int anchor) {
1160            mRules[verb] = anchor;
1161        }
1162
1163        /**
1164         * Retrieves a complete list of all supported rules, where the index is the rule
1165         * verb, and the element value is the value specified, or "false" if it was never
1166         * set.
1167         *
1168         * @return the supported rules
1169         * @see #addRule(int, int)
1170         */
1171        public int[] getRules() {
1172            return mRules;
1173        }
1174    }
1175
1176    private static class DependencyGraph {
1177        /**
1178         * List of all views in the graph.
1179         */
1180        private ArrayList<Node> mNodes = new ArrayList<Node>();
1181
1182        /**
1183         * List of nodes in the graph. Each node is identified by its
1184         * view id (see View#getId()).
1185         */
1186        private SparseArray<Node> mKeyNodes = new SparseArray<Node>();
1187
1188        /**
1189         * Temporary data structure used to build the list of roots
1190         * for this graph.
1191         */
1192        private LinkedList<Node> mRoots = new LinkedList<Node>();
1193
1194        /**
1195         * Clears the graph.
1196         */
1197        void clear() {
1198            final ArrayList<Node> nodes = mNodes;
1199            final int count = nodes.size();
1200
1201            for (int i = 0; i < count; i++) {
1202                nodes.get(i).release();
1203            }
1204            nodes.clear();
1205
1206            mKeyNodes.clear();
1207            mRoots.clear();
1208        }
1209
1210        /**
1211         * Adds a view to the graph.
1212         *
1213         * @param view The view to be added as a node to the graph.
1214         */
1215        void add(View view) {
1216            final int id = view.getId();
1217            final Node node = Node.acquire(view);
1218
1219            if (id != View.NO_ID) {
1220                mKeyNodes.put(id, node);
1221            }
1222
1223            mNodes.add(node);
1224        }
1225
1226        /**
1227         * Builds a sorted list of views. The sorting order depends on the dependencies
1228         * between the view. For instance, if view C needs view A to be processed first
1229         * and view A needs view B to be processed first, the dependency graph
1230         * is: B -> A -> C. The sorted array will contain views B, A and C in this order.
1231         *
1232         * @param sorted The sorted list of views. The length of this array must
1233         *        be equal to getChildCount().
1234         * @param rules The list of rules to take into account.
1235         */
1236        void getSortedViews(View[] sorted, int... rules) {
1237            final LinkedList<Node> roots = findRoots(rules);
1238            int index = 0;
1239
1240            while (roots.size() > 0) {
1241                final Node node = roots.removeFirst();
1242                final View view = node.view;
1243                final int key = view.getId();
1244
1245                sorted[index++] = view;
1246
1247                final HashSet<Node> dependents = node.dependents;
1248                for (Node dependent : dependents) {
1249                    final SparseArray<Node> dependencies = dependent.dependencies;
1250
1251                    dependencies.remove(key);
1252                    if (dependencies.size() == 0) {
1253                        roots.add(dependent);
1254                    }
1255                }
1256            }
1257
1258            if (index < sorted.length) {
1259                throw new IllegalStateException("Circular dependencies cannot exist"
1260                        + " in RelativeLayout");
1261            }
1262        }
1263
1264        /**
1265         * Finds the roots of the graph. A root is a node with no dependency and
1266         * with [0..n] dependents.
1267         *
1268         * @param rulesFilter The list of rules to consider when building the
1269         *        dependencies
1270         *
1271         * @return A list of node, each being a root of the graph
1272         */
1273        private LinkedList<Node> findRoots(int[] rulesFilter) {
1274            final SparseArray<Node> keyNodes = mKeyNodes;
1275            final ArrayList<Node> nodes = mNodes;
1276            final int count = nodes.size();
1277
1278            // Find roots can be invoked several times, so make sure to clear
1279            // all dependents and dependencies before running the algorithm
1280            for (int i = 0; i < count; i++) {
1281                final Node node = nodes.get(i);
1282                node.dependents.clear();
1283                node.dependencies.clear();
1284            }
1285
1286            // Builds up the dependents and dependencies for each node of the graph
1287            for (int i = 0; i < count; i++) {
1288                final Node node = nodes.get(i);
1289
1290                final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams();
1291                final int[] rules = layoutParams.mRules;
1292                final int rulesCount = rulesFilter.length;
1293
1294                // Look only the the rules passed in parameter, this way we build only the
1295                // dependencies for a specific set of rules
1296                for (int j = 0; j < rulesCount; j++) {
1297                    final int rule = rules[rulesFilter[j]];
1298                    if (rule > 0) {
1299                        // The node this node depends on
1300                        final Node dependency = keyNodes.get(rule);
1301                        // Skip unknowns and self dependencies
1302                        if (dependency == null || dependency == node) {
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