Fix Grid Starred Row/Column regressions (#11903)

* Prevent partial-starred columns from contracting Grid below width (fixes #11742)
Account for minimum row sizes when contracting rows (fixes #11835)

* Revert android project changes

* Drop comments

fixes #11742
fixes #11835
fixes #11796
This commit is contained in:
E.Z. Hart 2020-08-24 15:01:05 -06:00 коммит произвёл GitHub
Родитель 916af286a3
Коммит 22f5bf8f5d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 247 добавлений и 81 удалений

Просмотреть файл

@ -106,6 +106,171 @@ namespace Xamarin.Forms.Core.UnitTests
Assert.That(column0Height, Is.LessThan(gridHeight));
}
[Test(Description = "Columns with a Star width less than one should not cause the Grid to contract below the target width; see https://github.com/xamarin/Xamarin.Forms/issues/11742")]
public void StarWidthsLessThanOneShouldNotContractGrid()
{
var grid = new Grid
{
VerticalOptions = LayoutOptions.Start,
ColumnSpacing = 12
};
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(0.8, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
var label0 = new ColumnTestLabel
{
VerticalOptions = LayoutOptions.Start,
LineBreakMode = LineBreakMode.WordWrap,
Text = "This should wrap a bit"
};
var label1 = new ColumnTestLabel
{
VerticalOptions = LayoutOptions.Start,
LineBreakMode = LineBreakMode.WordWrap,
Text = "This ought to fit in the space just fine."
};
grid.Children.Add(label0, 0, 0);
grid.Children.Add(label1, 1, 0);
var gridWidth = 411;
grid.Measure(gridWidth, 1000);
var column0Width = grid.ColumnDefinitions[0].ActualWidth;
var column1Width = grid.ColumnDefinitions[1].ActualWidth;
Assert.That(column0Width, Is.LessThan(column1Width));
// Having a first column which is a fraction of a Star width should not cause the grid
// to contract below the target width
Assert.That(column0Width + column1Width, Is.GreaterThanOrEqualTo(gridWidth));
}
[Test]
public void ContractionAppliedEquallyOnMultiStarColumns()
{
var grid = new Grid { ColumnSpacing = 0 };
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(2, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
var smaller = new Size(100, 10);
var larger = new Size(200, 10);
var min = new Size(0, 10);
var label0 = new FixedSizeLabel(min, smaller)
{
Text = "label0"
};
var label1 = new FixedSizeLabel(min, larger)
{
Text = "label1"
};
grid.Children.Add(label0, 0, 0);
grid.Children.Add(label1, 1, 0);
// requested total width is 300, so this will force a contraction to 200
grid.Measure(200, 100);
var column0Width = grid.ColumnDefinitions[0].ActualWidth;
var column1Width = grid.ColumnDefinitions[1].ActualWidth;
Assert.That(column0Width, Is.EqualTo(column1Width / 2));
}
[Test]
public void AllStarColumnsCanOnlyContractToTheLargestMinimum()
{
var grid = new Grid { ColumnSpacing = 0 };
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
var leftColumn = new Size(100, 10);
var rightColumn = new Size(100, 10);
var largerMin = new Size(75, 10);
var smallerMin = new Size(50, 10);
var leftLabel = new FixedSizeLabel(largerMin, leftColumn)
{
Text = "label0"
};
var rightLabel = new FixedSizeLabel(smallerMin, rightColumn)
{
Text = "label1"
};
grid.Children.Add(leftLabel, 0, 0);
grid.Children.Add(rightLabel, 1, 0);
// requested total width is 200, so this will force an attemped contraction to 100
grid.Measure(100, 100);
var column0Width = grid.ColumnDefinitions[0].ActualWidth;
var column1Width = grid.ColumnDefinitions[1].ActualWidth;
Assert.That(column0Width, Is.EqualTo(column1Width));
}
[Test]
public void ContractionAppliedEquallyOnMultiStarRows()
{
var grid = new Grid { ColumnSpacing = 0, RowSpacing = 0 };
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(2, GridUnitType.Star) });
var smaller = new Size(100, 100);
var larger = new Size(100, 200);
var min = new Size(100, 0);
var label0 = new FixedSizeLabel(min, smaller)
{
Text = "label0"
};
var label1 = new FixedSizeLabel(min, larger)
{
Text = "label1"
};
grid.Children.Add(label0, 0, 0);
grid.Children.Add(label1, 1, 0);
// requested total height is 300, so this will force a contraction to 200
grid.Measure(200, 200);
var column0Height = grid.RowDefinitions[0].ActualHeight;
var column1Height = grid.RowDefinitions[1].ActualHeight;
Assert.That(column0Height, Is.EqualTo(column1Height / 2));
}
class FixedSizeLabel : Label
{
readonly Size _minimumSize;
readonly Size _requestedSize;
public FixedSizeLabel(Size minimumSize, Size requestedSize)
{
IsPlatformEnabled = true;
_minimumSize = minimumSize;
_requestedSize = requestedSize;
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
return new SizeRequest(_requestedSize, _minimumSize);
}
}
class ColumnTestLabel : Label
{
public ColumnTestLabel()

Просмотреть файл

@ -220,7 +220,7 @@ namespace Xamarin.Forms
return rowHeightSum;
}
Size ComputeCurrentSize()
Size ComputeCurrentSize()
{
var columnWidthSum = ComputeColumnWidthSum();
var rowHeightSum = ComputeRowHeightSum();
@ -318,7 +318,14 @@ namespace Xamarin.Forms
continue;
}
starColumnWidth = column.ActualWidth;
if (starColumnWidth == 0)
{
starColumnWidth = column.ActualWidth;
}
else
{
starColumnWidth = Math.Min(column.ActualWidth, starColumnWidth);
}
if (column.MinimumWidth > starColumnMinWidth)
{
@ -335,13 +342,10 @@ namespace Xamarin.Forms
// contract as much as we can but no more
double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Width - targetWidth, 0));
double contractFactor = contractionNeeded / contractionSpace;
var delta = contractFactor * starColumnWidth;
if (starColumnWidth - delta <= starColumnMinWidth)
{
delta = starColumnWidth - starColumnMinWidth;
}
double contractionFactor = contractionNeeded / contractionSpace;
var delta = contractionFactor >= 1
? starColumnWidth - starColumnMinWidth
: contractionFactor * (starColumnWidth - starColumnMinWidth);
for (var index = 0; index < _columns.Count; index++)
{
@ -351,11 +355,11 @@ namespace Xamarin.Forms
continue;
}
column.ActualWidth -= delta;
column.ActualWidth -= delta * column.Width.Value;
}
}
void ContractStarRowsIfNeeded(double targetHeight)
void ContractStarRowsIfNeeded(double targetHeight)
{
var request = ComputeCurrentSize();
@ -377,7 +381,14 @@ namespace Xamarin.Forms
continue;
}
starRowHeight = row.ActualHeight;
if (starRowHeight == 0)
{
starRowHeight = row.ActualHeight;
}
else
{
starRowHeight = Math.Min(row.ActualHeight, starRowHeight);
}
if (row.MinimumHeight > starRowMinHeight)
{
@ -394,12 +405,9 @@ namespace Xamarin.Forms
double contractionNeeded = Math.Min(contractionSpace, Math.Max(request.Height - targetHeight, 0));
double contractionFactor = contractionNeeded / contractionSpace;
var delta = contractionFactor * starRowHeight;
if (starRowHeight - delta <= starRowMinHeight)
{
delta = starRowHeight - starRowMinHeight;
}
var delta = contractionFactor >= 1
? starRowHeight - starRowMinHeight
: contractionFactor * (starRowHeight - starRowMinHeight);
for (var index = 0; index < _rows.Count; index++)
{
@ -409,7 +417,7 @@ namespace Xamarin.Forms
continue;
}
row.ActualHeight -= delta;
row.ActualHeight -= delta * row.Height.Value;
}
}
@ -535,7 +543,7 @@ namespace Xamarin.Forms
void MeasureAndContractStarredRows(double width, double height, double totalStarsHeight)
{
double starRowHeight;
starRowHeight = MeasureStarredRows();
starRowHeight = MeasureStarredRows(width, height);
if (!double.IsPositiveInfinity(height) && double.IsPositiveInfinity(width))
{
@ -558,59 +566,7 @@ namespace Xamarin.Forms
ContractStarRowsIfNeeded(height);
}
double MeasuredStarredColumns(double widthConstraint, double heightConstraint)
{
double starColWidth;
for (var iteration = 0; iteration < 2; iteration++)
{
for (var colspan = 1; colspan <= _columns.Count; colspan++)
{
for (var i = 0; i < _columns.Count; i++)
{
ColumnDefinition col = _columns[i];
if (!col.Width.IsStar)
continue;
if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip
continue;
double actualWidth = col.ActualWidth;
double minimumWidth = col.MinimumWidth;
for (var index = 0; index < InternalChildren.Count; index++)
{
var child = (View)InternalChildren[index];
if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1)
continue;
double assignedWidth = GetAssignedColumnWidth(child);
// Can we start with a more reasonable constraint here? if our starred column count is greater than 1, widthConstraint _has_ to be too big
SizeRequest sizeRequest = child.Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins);
actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing);
minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing);
}
if (actualWidth >= 0)
col.ActualWidth = actualWidth;
if (minimumWidth >= 0)
col.MinimumWidth = minimumWidth;
}
}
}
//Measure the stars
starColWidth = 1;
for (var index = 0; index < _columns.Count; index++)
{
ColumnDefinition col = _columns[index];
if (!col.Width.IsStar)
continue;
starColWidth = col.Width.Value != 0 ? Math.Max(starColWidth, col.ActualWidth / col.Width.Value) : 0;
}
return starColWidth;
}
void MeasureGrid(double width, double height, bool requestSize = false)
{
EnsureRowsColumnsInitialized();
@ -657,7 +613,56 @@ namespace Xamarin.Forms
ExpandLastAutoColumnIfNeeded(width, requestSize);
}
double MeasureStarredRows()
double MeasuredStarredColumns(double widthConstraint, double heightConstraint)
{
double starColWidth;
for (var iteration = 0; iteration < 2; iteration++)
{
for (var colspan = 1; colspan <= _columns.Count; colspan++)
{
for (var i = 0; i < _columns.Count; i++)
{
ColumnDefinition col = _columns[i];
if (!col.Width.IsStar)
continue;
if (col.ActualWidth >= 0) // if Actual is already set (by a smaller span), skip
continue;
double actualWidth = col.ActualWidth;
double minimumWidth = col.MinimumWidth;
for (var index = 0; index < InternalChildren.Count; index++)
{
var child = (View)InternalChildren[index];
if (!child.IsVisible || GetColumnSpan(child) != colspan || !IsInColumn(child, i) || NumberOfUnsetColumnWidth(child) > 1)
continue;
double assignedWidth = GetAssignedColumnWidth(child);
SizeRequest sizeRequest = child.Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins);
actualWidth = Math.Max(actualWidth, sizeRequest.Request.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing);
minimumWidth = Math.Max(minimumWidth, sizeRequest.Minimum.Width - assignedWidth - (GetColumnSpan(child) - 1) * ColumnSpacing);
}
if (actualWidth >= 0)
col.ActualWidth = actualWidth;
if (minimumWidth >= 0)
col.MinimumWidth = minimumWidth;
}
}
}
starColWidth = 1;
for (var index = 0; index < _columns.Count; index++)
{
ColumnDefinition col = _columns[index];
if (!col.Width.IsStar)
continue;
starColWidth = col.Width.Value != 0 ? Math.Max(starColWidth, col.ActualWidth / col.Width.Value) : 0;
}
return starColWidth;
}
double MeasureStarredRows(double widthConstraint, double heightConstraint)
{
double starRowHeight;
for (var iteration = 0; iteration < 2; iteration++)
@ -680,11 +685,10 @@ namespace Xamarin.Forms
if (!child.IsVisible || GetRowSpan(child) != rowspan || !IsInRow(child, i) || NumberOfUnsetRowHeight(child) > 1)
continue;
double assignedHeight = GetAssignedRowHeight(child);
double assignedWidth = GetAssignedColumnWidth(child);
SizeRequest sizeRequest = child.Measure(assignedWidth, double.PositiveInfinity, MeasureFlags.IncludeMargins);
actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1));
minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - RowSpacing * (GetRowSpan(child) - 1));
SizeRequest sizeRequest = child.Measure(widthConstraint, heightConstraint, MeasureFlags.IncludeMargins);
actualHeight = Math.Max(actualHeight, sizeRequest.Request.Height - assignedHeight - (GetRowSpan(child) - 1) * RowSpacing);
minimumHeight = Math.Max(minimumHeight, sizeRequest.Minimum.Height - assignedHeight - (GetRowSpan(child) - 1) * RowSpacing);
}
if (actualHeight >= 0)
row.ActualHeight = actualHeight;
@ -695,9 +699,6 @@ namespace Xamarin.Forms
}
}
// 3. Star columns:
//Measure the stars
starRowHeight = 1;
for (var index = 0; index < _rows.Count; index++)
{