Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4133751d authored by Android (Google) Code Review's avatar Android (Google) Code Review
Browse files

Merge change 5129 into donut

* changes:
  Improve RelativeLayout by allowing dependencies to be declared in a random order.
parents 3fbd946b 725015a9
Loading
Loading
Loading
Loading
+385 −37
Original line number Diff line number Diff line
@@ -20,8 +20,15 @@ import com.android.internal.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.Poolable;
import android.util.Pool;
import android.util.Pools;
import android.util.PoolableManager;
import static android.util.Log.d;
import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
@@ -32,6 +39,9 @@ import android.widget.RemoteViews.RemoteView;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.HashSet;

/**
 * A Layout where the positions of the children can be described in relation to each other or to the
@@ -55,6 +65,10 @@ import java.util.TreeSet;
 */
@RemoteView
public class RelativeLayout extends ViewGroup {
    private static final String LOG_TAG = "RelativeLayout";

    private static final boolean DEBUG_GRAPH = false;

    public static final int TRUE = -1;

    /**
@@ -142,7 +156,12 @@ public class RelativeLayout extends ViewGroup {
    private final Rect mSelfBounds = new Rect();
    private int mIgnoreGravity;

    private static SortedSet<View> mTopToBottomLeftToRightSet = null;
    private SortedSet<View> mTopToBottomLeftToRightSet = null;
    
    private boolean mDirtyHierarchy;
    private View[] mSortedHorizontalChildren = new View[0];
    private View[] mSortedVerticalChildren = new View[0];
    private final DependencyGraph mGraph = new DependencyGraph();

    public RelativeLayout(Context context) {
        super(context);
@@ -231,8 +250,44 @@ public class RelativeLayout extends ViewGroup {
        return mBaselineView != null ? mBaselineView.getBaseline() : super.getBaseline();
    }

    @Override
    public void requestLayout() {
        super.requestLayout();
        mDirtyHierarchy = true;
    }

    private void sortChildren() {
        int count = getChildCount();
        if (mSortedVerticalChildren.length != count) mSortedVerticalChildren = new View[count];
        if (mSortedHorizontalChildren.length != count) mSortedHorizontalChildren = new View[count];

        final DependencyGraph graph = mGraph;
        graph.clear();

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            graph.add(child);
        }

        if (DEBUG_GRAPH) {
            d(LOG_TAG, "=== Sorted vertical children");
            graph.log(getResources(), ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM);
            d(LOG_TAG, "=== Sorted horizontal children");
            graph.log(getResources(), LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT);
        }

        graph.getSortedViews(mSortedVerticalChildren, ABOVE, BELOW, ALIGN_BASELINE,
                ALIGN_TOP, ALIGN_BOTTOM);
        graph.getSortedViews(mSortedHorizontalChildren, LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mDirtyHierarchy) {
            mDirtyHierarchy = false;
            sortChildren();
        }

        int myWidth = -1;
        int myHeight = -1;

@@ -261,7 +316,6 @@ public class RelativeLayout extends ViewGroup {
            height = myHeight;
        }

        int len = this.getChildCount();
        mHasBaselineAlignedChild = false;

        View ignore = null;
@@ -279,13 +333,28 @@ public class RelativeLayout extends ViewGroup {
            ignore = findViewById(mIgnoreGravity);
        }

        for (int i = 0; i < len; i++) {
            View child = getChildAt(i);

        View[] views = mSortedVerticalChildren;
        int count = views.length;
        for (int i = 0; i < count; i++) {
            View child = views[i];
            if (child.getVisibility() != GONE) {
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                applyVerticalSizeRules(params, myHeight);
                measureChildVertical(child, params, myHeight);
                positionChildVertical(child, params, myHeight);
            }
        }

        views = mSortedHorizontalChildren;
        count = views.length;
        for (int i = 0; i < count; i++) {
            View child = views[i];
            if (child.getVisibility() != GONE) {
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                applySizeRules(params, myWidth, myHeight);
                applyHorizontalSizeRules(params, myWidth);
                measureChild(child, params, myWidth, myHeight);
                positionChild(child, params, myWidth, myHeight);
                positionChildHorizontal(child, params, myWidth);

                if (widthMode != MeasureSpec.EXACTLY) {
                    width = Math.max(width, params.mRight);
@@ -307,7 +376,7 @@ public class RelativeLayout extends ViewGroup {
        }

        if (mHasBaselineAlignedChild) {
            for (int i = 0; i < len; i++) {
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    LayoutParams params = (LayoutParams) child.getLayoutParams();
@@ -362,7 +431,7 @@ public class RelativeLayout extends ViewGroup {
            final int horizontalOffset = contentBounds.left - left;
            final int verticalOffset = contentBounds.top - top;
            if (horizontalOffset != 0 || verticalOffset != 0) {
                for (int i = 0; i < len; i++) {
                for (int i = 0; i < count; i++) {
                    View child = getChildAt(i);
                    if (child.getVisibility() != GONE && child != ignore) {
                        LayoutParams params = (LayoutParams) child.getLayoutParams();
@@ -416,9 +485,7 @@ public class RelativeLayout extends ViewGroup {
     * @param myWidth Width of the the RelativeLayout
     * @param myHeight Height of the RelativeLayout
     */
    private void measureChild(View child, LayoutParams params, int myWidth,
            int myHeight) {

    private void measureChild(View child, LayoutParams params, int myWidth, int myHeight) {
        int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft,
                params.mRight, params.width,
                params.leftMargin, params.rightMargin,
@@ -432,6 +499,16 @@ public class RelativeLayout extends ViewGroup {
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    private void measureChildVertical(View child, LayoutParams params, int myHeight) {
        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        int childHeightMeasureSpec = getChildMeasureSpec(params.mTop,
                params.mBottom, params.height,
                params.topMargin, params.bottomMargin,
                mPaddingTop, mPaddingBottom,
                myHeight);
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    /**
     * Get a measure spec that accounts for all of the constraints on this view.
     * This includes size contstraints imposed by the RelativeLayout as well as
@@ -511,19 +588,7 @@ public class RelativeLayout extends ViewGroup {
        return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
    }

    /**
     * After the child has been measured, assign it a position. Some views may
     * already have final values for l,t,r,b. Others may have one or both edges
     * unfixed (i.e. set to -1) in each dimension. These will get positioned
     * based on which edge is fixed, the view's desired dimension, and whether
     * or not it is centered.
     *
     * @param child Child to position
     * @param params LayoutParams associated with child
     * @param myWidth Width of the the RelativeLayout
     * @param myHeight Height of the RelativeLayout
     */
    private void positionChild(View child, LayoutParams params, int myWidth, int myHeight) {
    private void positionChildHorizontal(View child, LayoutParams params, int myWidth) {
        int[] rules = params.getRules();

        if (params.mLeft < 0 && params.mRight >= 0) {
@@ -541,6 +606,10 @@ public class RelativeLayout extends ViewGroup {
                params.mRight = params.mLeft + child.getMeasuredWidth();
            }
        }
    }

    private void positionChildVertical(View child, LayoutParams params, int myHeight) {
        int[] rules = params.getRules();

        if (params.mTop < 0 && params.mBottom >= 0) {
            // Bottom is fixed, but top varies
@@ -559,17 +628,7 @@ public class RelativeLayout extends ViewGroup {
        }
    }

    /**
     * Set l,t,r,b values in the LayoutParams for one view based on its layout rules.
     * Big assumption #1: All antecedents of this view have been sized & positioned
     * Big assumption #2: The dimensions of the parent view (the RelativeLayout)
     * are already known if they are needed.
     *
     * @param childParams LayoutParams for the view being positioned
     * @param myWidth Width of the the RelativeLayout
     * @param myHeight Height of the RelativeLayout
     */
    private void applySizeRules(LayoutParams childParams, int myWidth, int myHeight) {
    private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) {
        int[] rules = childParams.getRules();
        RelativeLayout.LayoutParams anchorParams;

@@ -629,6 +688,11 @@ public class RelativeLayout extends ViewGroup {
                // FIXME uh oh...
            }
        }
    }

    private void applyVerticalSizeRules(LayoutParams childParams, int myHeight) {
        int[] rules = childParams.getRules();
        RelativeLayout.LayoutParams anchorParams;

        childParams.mTop = -1;
        childParams.mBottom = -1;
@@ -1033,4 +1097,288 @@ public class RelativeLayout extends ViewGroup {
            return mRules;
        }
    }

    private static class DependencyGraph {
        /**
         * List of views with no id. These views cannot be dependencies of
         * other views, so treat the apart for faster processing.
         */
        private ArrayList<View> mNakedRoots = new ArrayList<View>();

        /**
         * List of nodes in the graph. Each node is identified by its
         * view id (see View#getId()).
         */
        private SparseArray<Node> mNodes = new SparseArray<Node>();

        /**
         * Temporary data structure used to build the list of roots
         * for this graph.
         */
        private LinkedList<Node> mRoots = new LinkedList<Node>();

        /**
         * Clears the graph.
         */
        void clear() {
            final SparseArray<Node> nodes = mNodes;
            final int count = nodes.size();

            for (int i = 0; i < count; i++) {
                nodes.valueAt(i).release();
            }
            nodes.clear();

            mNakedRoots.clear();
            mRoots.clear();
        }

        /**
         * Adds a view to the graph.
         *
         * @param view The view to be added as a node to the graph.
         */
        void add(View view) {
            final int id = view.getId();

            if (id != View.NO_ID) {
                mNodes.put(id, Node.acquire(view));
            } else {
                mNakedRoots.add(view);
            }
        }

        /**
         * Builds a sorted list of views. The sorting order depends on the dependencies
         * between the view. For instance, if view C needs view A to be processed first
         * and view A needs view B to be processed first, the dependency graph
         * is: B -> A -> C. The sorted array will contain views B, A and C in this order.
         *
         * @param sorted The sorted list of views. The length of this array must
         *        be equal to getChildCount().
         * @param rules The list of rules to take into account.
         */
        void getSortedViews(View[] sorted, int... rules) {
            final LinkedList<Node> roots = findRoots(rules);
            int index = 0;

            final ArrayList<View> nakedRoots = mNakedRoots;
            final int count = nakedRoots.size();
            for ( ; index < count; index++) {
                sorted[index] = nakedRoots.get(index);
            }

            while (roots.size() > 0) {
                final Node node = roots.removeFirst();
                final View view = node.view;
                final int key = view.getId();

                sorted[index++] = view;

                final HashSet<Node> dependents = node.dependents;
                for (Node dependent : dependents) {
                    final SparseArray<Node> dependencies = dependent.dependencies;

                    dependencies.remove(key);
                    if (dependencies.size() == 0) {
                        roots.add(dependent);
                    }
                }
            }

            if (index < sorted.length) {
                throw new IllegalStateException("Circular dependencies cannot exist"
                        + " in RelativeLayout");
            }
        }

        /**
         * Finds the roots of the graph. A root is a node with no dependency and
         * with [0..n] dependents.
         *
         * @param rulesFilter The list of rules to consider when building the
         *        dependencies
         *
         * @return A list of node, each being a root of the graph
         */
        private LinkedList<Node> findRoots(int[] rulesFilter) {
            final SparseArray<Node> nodes = mNodes;
            final int count = nodes.size();

            // Find roots can be invoked several times, so make sure to clear
            // all dependents and dependencies before running the algorithm
            for (int i = 0; i < count; i++) {
                final Node node = nodes.valueAt(i);
                node.dependents.clear();
                node.dependencies.clear();
            }

            // Builds up the dependents and dependencies for each node of the graph
            for (int i = 0; i < count; i++) {
                final Node node = nodes.valueAt(i);

                final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams();
                final int[] rules = layoutParams.mRules;
                final int rulesCount = rulesFilter.length;

                // Look only the the rules passed in parameter, this way we build only the
                // dependencies for a specific set of rules
                for (int j = 0; j < rulesCount; j++) {
                    final int rule = rules[rulesFilter[j]];
                    if (rule > 0) {
                        // The node this node depends on
                        final Node dependency = nodes.get(rule);
                        if (dependency == node) {
                            throw new IllegalStateException("A view cannot have a dependency" +
                                    " on itself");
                        }
                        // Add the current node as a dependent
                        dependency.dependents.add(node);
                        // Add a dependency to the current node
                        node.dependencies.put(rule, dependency);
                    }
                }
            }

            final LinkedList<Node> roots = mRoots;
            roots.clear();

            // Finds all the roots in the graph: all nodes with no dependencies
            for (int i = 0; i < count; i++) {
                final Node node = nodes.valueAt(i);
                if (node.dependencies.size() == 0) roots.add(node);
            }

            return roots;
        }

        /**
         * Prints the dependency graph for the specified rules.
         *
         * @param resources The context's resources to print the ids.
         * @param rules The list of rules to take into account.
         */
        void log(Resources resources, int... rules) {
            for (View view : mNakedRoots) {
                printViewId(resources, view);
            }

            final LinkedList<Node> roots = findRoots(rules);
            for (Node node : roots) {
                printNode(resources, node);
            }
        }

        private static void printViewId(Resources resources, View view) {
            if (view.getId() != View.NO_ID) {
                d(LOG_TAG, resources.getResourceEntryName(view.getId()));
            } else {
                d(LOG_TAG, "NO_ID");
            }
        }

        private static void appendViewId(Resources resources, Node node, StringBuilder buffer) {
            if (node.view.getId() != View.NO_ID) {
                buffer.append(resources.getResourceEntryName(node.view.getId()));
            } else {
                buffer.append("NO_ID");
            }
        }

        private static void printNode(Resources resources, Node node) {
            if (node.dependents.size() == 0) {
                printViewId(resources, node.view);
            } else {
                for (Node dependent : node.dependents) {
                    StringBuilder buffer = new StringBuilder();
                    appendViewId(resources, node, buffer);
                    printdependents(resources, dependent, buffer);
                }
            }
        }

        private static void printdependents(Resources resources, Node node, StringBuilder buffer) {
            buffer.append(" -> ");
            appendViewId(resources, node, buffer);

            if (node.dependents.size() == 0) {
                d(LOG_TAG, buffer.toString());
            } else {
                for (Node dependent : node.dependents) {
                    StringBuilder subBuffer = new StringBuilder(buffer);
                    printdependents(resources, dependent, subBuffer);
                }
            }
        }

        /**
         * A node in the dependency graph. A node is a view, its list of dependencies
         * and its list of dependents.
         *
         * A node with no dependent is considered a root of the graph.
         */
        static class Node implements Poolable<Node> {
            /**
             * The view representing this node in the layout.
             */
            View view;

            /**
             * The list of dependents for this node; a dependent is a node
             * that needs this node to be processed first.
             */
            final HashSet<Node> dependents = new HashSet<Node>();

            /**
             * The list of dependencies for this node.
             */
            final SparseArray<Node> dependencies = new SparseArray<Node>();

            /*
             * START POOL IMPLEMENTATION
             */
            private static final int POOL_LIMIT = 12;
            private static final Pool<Node> sPool = Pools.synchronizedPool(
                    Pools.finitePool(new PoolableManager<Node>() {
                        public Node newInstance() {
                            return new Node();
                        }

                        public void onAcquired(Node element) {
                        }

                        public void onReleased(Node element) {
                        }
                    }, POOL_LIMIT)
            );

            private Node mNext;

            public void setNextPoolable(Node element) {
                mNext = element;
            }

            public Node getNextPoolable() {
                return mNext;
            }

            static Node acquire(View view) {
                final Node node = sPool.acquire();
                node.view = view;

                return node;
            }

            void release() {
                view = null;
                dependents.clear();
                dependencies.clear();

                sPool.release(this);
            }
            /*
             * END POOL IMPLEMENTATION
             */
        }
    }
}