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