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