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

Commit 98d5f04f authored by Yigit Boyar's avatar Yigit Boyar Committed by Philip Milne
Browse files

Improve GridLayout's weight calculations

This CL improves the method by which excess space is distributed in GridLayout.
Previously, GridLayout would assume weights were arranged in a 'line' and
sum the weights in the assumed line to figure out the proportional allocation
to each view. The system involved running GridLayout's internal constraint 
solver twice.

Behavior was unspecified (and surprising) when weights appeared in views
that were not linked together linearly, typically leaving the last view 
in each axis with more space than expected (in GridLayout's Bellman-Ford 
constraint solver, remaining space goes to the last span of the axis).

This CL changes the weight distribution mechanism to effectively integrate it
with the Bellman-Ford constraint resolution algorithm. It does this
by returning a boolean value from the constraint solver saying whether or
not the constraints could be solved and then using a binary chop to find 
a maximum amount of space that can be distributed without violating the 
constraints.

This implementation runs the solver log(<axis size> * <number of Views>)
times until finding the maximum amount of space that can be distributed according
to the weights without causing a contradiction. We expect the cost of this
variation to be around a factor of 10 worse than the previous implementation
but to provide a simple and general definition of space distribution via
weights that will be open to many future optimizations.

As a side effect, this CL also fixes a bug in GridLayout where remaining space
was distributed only along the major axis.

Bug: 17485996
Change-Id: I120f39e95e90b5b35072ef8a6c348ec541aae42a
parent fb11949d
Loading
Loading
Loading
Loading
+57 −24
Original line number Diff line number Diff line
@@ -1613,7 +1613,11 @@ public class GridLayout extends ViewGroup {
        equivalent to the single-source shortest paths problem on a digraph, for
        which the O(n^2) Bellman-Ford algorithm the most commonly used general solution.
        */
        private void solve(Arc[] arcs, int[] locations) {
        private boolean solve(Arc[] arcs, int[] locations) {
            return solve(arcs, locations, true);
        }

        private boolean solve(Arc[] arcs, int[] locations, boolean modifyOnError) {
            String axisName = horizontal ? "horizontal" : "vertical";
            int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1.
            boolean[] originalCulprits = null;
@@ -1631,10 +1635,14 @@ public class GridLayout extends ViewGroup {
                        if (originalCulprits != null) {
                            logError(axisName, arcs, originalCulprits);
                        }
                        return;
                        return true;
                    }
                }

                if (!modifyOnError) {
                    return false; // cannot solve with these constraints
                }

                boolean[] culprits = new boolean[arcs.length];
                for (int i = 0; i < N; i++) {
                    for (int j = 0, length = arcs.length; j < length; j++) {
@@ -1658,6 +1666,7 @@ public class GridLayout extends ViewGroup {
                    }
                }
            }
            return true;
        }

        private void computeMargins(boolean leading) {
@@ -1697,8 +1706,8 @@ public class GridLayout extends ViewGroup {
            return trailingMargins;
        }

        private void solve(int[] a) {
            solve(getArcs(), a);
        private boolean solve(int[] a) {
            return solve(getArcs(), a);
        }

        private boolean computeHasWeights() {
@@ -1740,28 +1749,18 @@ public class GridLayout extends ViewGroup {
            return deltas;
        }

        private void shareOutDelta() {
            int totalDelta = 0;
            float totalWeight = 0;
        private void shareOutDelta(int totalDelta, float totalWeight) {
            Arrays.fill(deltas, 0);
            for (int i = 0, N = getChildCount(); i < N; i++) {
                View c = getChildAt(i);
                LayoutParams lp = getLayoutParams(c);
                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
                float weight = spec.weight;
                if (weight != 0) {
                    int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i];
                    totalDelta += delta;
                    totalWeight += weight;
                }
            }
            for (int i = 0, N = getChildCount(); i < N; i++) {
                LayoutParams lp = getLayoutParams(getChildAt(i));
                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
                float weight = spec.weight;
                if (weight != 0) {
                    int delta = Math.round((weight * totalDelta / totalWeight));
                    deltas[i] = delta;
                    // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end
                    // the two adjustments below are to counter the above rounding and avoid
                    // off-by-ones at the end
                    totalDelta -= delta;
                    totalWeight -= weight;
                }
@@ -1771,13 +1770,47 @@ public class GridLayout extends ViewGroup {
        private void solveAndDistributeSpace(int[] a) {
            Arrays.fill(getDeltas(), 0);
            solve(a);
            shareOutDelta();
            arcsValid = false;
            forwardLinksValid = false;
            backwardLinksValid = false;
            groupBoundsValid = false;
            int deltaMax = parentMin.value * getChildCount() + 1; //exclusive
            if (deltaMax < 2) {
                return; //don't have any delta to distribute
            }
            int deltaMin = 0; //inclusive

            float totalWeight = calculateTotalWeight();

            int validDelta = -1; //delta for which a solution exists
            boolean validSolution = true;
            // do a binary search to find the max delta that won't conflict with constraints
            while(deltaMin < deltaMax) {
                final int delta = (deltaMin + deltaMax) / 2;
                invalidateValues();
                shareOutDelta(delta, totalWeight);
                validSolution = solve(getArcs(), a, false);
                if (validSolution) {
                    validDelta = delta;
                    deltaMin = delta + 1;
                } else {
                    deltaMax = delta;
                }
            }
            if (validDelta > 0 && !validSolution) {
                // last solution was not successful but we have a successful one. Use it.
                invalidateValues();
                shareOutDelta(validDelta, totalWeight);
                solve(a);
            }
        }

        private float calculateTotalWeight() {
            float totalWeight = 0f;
            for (int i = 0, N = getChildCount(); i < N; i++) {
                View c = getChildAt(i);
                LayoutParams lp = getLayoutParams(c);
                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
                totalWeight += spec.weight;
            }
            return totalWeight;
        }

        private void computeLocations(int[] a) {
            if (!hasWeights()) {