From 98d5f04f91580bf8b517c17132eeb8d42008b61d Mon Sep 17 00:00:00 2001 From: Yigit Boyar Date: Tue, 18 Nov 2014 15:47:56 -0800 Subject: [PATCH] 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( * ) 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 --- core/java/android/widget/GridLayout.java | 81 +++++++++++++++++------- 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index defc26ccabec5..161ae7ec33a73 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -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,12 +1770,46 @@ 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; - solve(a); + 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) {