diff --git a/src/Avalonia.Controls/Utils/GridLayout.cs b/src/Avalonia.Controls/Utils/GridLayout.cs
new file mode 100644
index 0000000000..2497a5f46c
--- /dev/null
+++ b/src/Avalonia.Controls/Utils/GridLayout.cs
@@ -0,0 +1,144 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using JetBrains.Annotations;
+
+namespace Avalonia.Controls.Utils
+{
+ internal class GridLayout
+ {
+ internal GridLayout(LengthDefinitions lengths)
+ {
+ _lengths = lengths;
+ }
+
+ private readonly LengthDefinitions _lengths;
+
+ ///
+ /// Try to calculate the lengths that will be used to measure the children.
+ /// If the is not enough, we'll even not compress the measure length.
+ ///
+ /// The container length, width or height.
+ /// The lengths that can be used to measure the children.
+ [Pure]
+ internal List Measure(double containerLength)
+ {
+ var lengths = _lengths.Clone();
+
+ // Exclude all the pixel lengths, so that we can calculate the star lengths.
+ containerLength -= lengths
+ .Where(x => x.Length.IsAbsolute)
+ .Aggregate(0.0, (sum, add) => sum + add.Length.Value);
+
+ // Aggregate the star count, so that we can determine the length of each star unit.
+ var starCount = lengths
+ .Where(x => x.Length.IsStar)
+ .Aggregate(0.0, (sum, add) => sum + add.Length.Value);
+ // There is no need to care the (starCount == 0). If this happens, we'll ignore all the stars.
+ var starUnitLength = containerLength / starCount;
+
+ // If there is no stars, just return all pixels.
+ if (Equals(starCount, 0.0))
+ {
+ return lengths.Select(x => x.Length.IsAuto ? double.PositiveInfinity : x.Length.Value).ToList();
+ }
+
+ // ---
+ // Warning! The code below will start to change the lengths item value.
+ // ---
+
+ // Exclude the star unit if its min/max length range does not contain the calculated star length.
+ var intermediateStarLengths = lengths.Where(x => x.Length.IsStar).ToList();
+ // Indicate whether all star lengths are in range of min and max or not.
+ var allInRange = false;
+ while (!allInRange)
+ {
+ foreach (var length in intermediateStarLengths)
+ {
+ // Find out if there is any length out of min to max.
+ var (star, min, max) = (length.Length.Value, length.MinLength, length.MaxLength);
+ var starLength = star * starUnitLength;
+ if (starLength < min || starLength > max)
+ {
+ // If the star length is out of min to max, change it to a pixel unit.
+ if (starLength < min)
+ {
+ length.Update(min);
+ starLength = min;
+ }
+ else if (starLength > max)
+ {
+ length.Update(max);
+ starLength = max;
+ }
+
+ // Update the rest star length info.
+ intermediateStarLengths.Remove(length);
+ containerLength -= starLength;
+ starCount -= star;
+ starUnitLength = containerLength / starCount;
+ break;
+ }
+ }
+
+ // All lengths are in range, so that we have enough lengths to measure children.
+ allInRange = true;
+ foreach (var length in intermediateStarLengths)
+ {
+ length.Update(length.Length.Value * starUnitLength);
+ }
+ }
+
+ // Return the modified lengths as measuring lengths.
+ return lengths.Select(x =>
+ x.Length.GridUnitType == GridUnitType.Auto
+ ? double.PositiveInfinity
+ : x.Length.Value).ToList();
+ }
+
+ internal class LengthDefinitions : IEnumerable, ICloneable
+ {
+ private readonly List _lengths;
+
+ private LengthDefinitions(IEnumerable lengths)
+ {
+ _lengths = lengths.ToList();
+ }
+
+ public IEnumerator GetEnumerator() => _lengths.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ object ICloneable.Clone() => Clone();
+
+ public LengthDefinitions Clone() => new LengthDefinitions(
+ _lengths.Select(x => new LengthDefinition(x.Length, x.MinLength, x.MaxLength)));
+
+ public static implicit operator LengthDefinitions(RowDefinitions rows)
+ => new LengthDefinitions(rows.Select(x => (LengthDefinition) x));
+ }
+
+ internal class LengthDefinition
+ {
+ internal LengthDefinition(GridLength length, double minLength, double maxLength)
+ {
+ Length = length;
+ MinLength = minLength;
+ MaxLength = maxLength;
+ }
+
+ internal GridLength Length { get; private set; }
+ internal double MinLength { get; }
+ internal double MaxLength { get; }
+
+ public static implicit operator LengthDefinition(RowDefinition row)
+ => new LengthDefinition(row.Height, row.MinHeight, row.MaxHeight);
+
+ public void Update(double pixel)
+ {
+ Length = new GridLength(pixel);
+ }
+ }
+ }
+}
diff --git a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs
new file mode 100644
index 0000000000..be6b6ce465
--- /dev/null
+++ b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs
@@ -0,0 +1,99 @@
+using Avalonia.Controls.Utils;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests
+{
+ public class GridLayoutTests
+ {
+ [Fact]
+ public void Measure_AllPixelLength_Correct()
+ {
+ // Arrange
+ var layout = new GridLayout(new RowDefinitions("100,200,300"));
+
+ // Action
+ var measure = layout.Measure(800);
+
+ // Assert
+ Assert.Equal(measure, new [] { 100d, 200d, 300d });
+ }
+
+ [Fact]
+ public void Measure_AllStarLength_Correct()
+ {
+ // Arrange
+ var layout = new GridLayout(new RowDefinitions("*,2*,3*"));
+
+ // Action
+ var measure = layout.Measure(600);
+
+ // Assert
+ Assert.Equal(measure, new [] { 100d, 200d, 300d });
+ }
+
+ [Fact]
+ public void Measure_MixStarPixelLength_Correct()
+ {
+ // Arrange
+ var layout = new GridLayout(new RowDefinitions("100,2*,3*"));
+
+ // Action
+ var measure = layout.Measure(600);
+
+ // Assert
+ Assert.Equal(measure, new [] { 100d, 200d, 300d });
+ }
+
+ [Fact]
+ public void Measure_MixAutoPixelLength_Correct()
+ {
+ // Arrange
+ var layout = new GridLayout(new RowDefinitions("100,200,Auto"));
+
+ // Action
+ var measure = layout.Measure(600);
+
+ // Assert
+ Assert.Equal(measure, new [] { 100d, 200d, double.PositiveInfinity });
+ }
+
+ [Fact]
+ public void Measure_MixAutoStarLength_Correct()
+ {
+ // Arrange
+ var layout = new GridLayout(new RowDefinitions("*,2*,Auto"));
+
+ // Action
+ var measure = layout.Measure(600);
+
+ // Assert
+ Assert.Equal(measure, new[] { 200d, 400d, double.PositiveInfinity });
+ }
+
+ [Fact]
+ public void Measure_MixAutoStarPixelLength_Correct()
+ {
+ // Arrange
+ var layout = new GridLayout(new RowDefinitions("*,200,Auto"));
+
+ // Action
+ var measure = layout.Measure(600);
+
+ // Assert
+ Assert.Equal(measure, new[] { 400d, 200d, double.PositiveInfinity });
+ }
+
+ [Fact]
+ public void Measure_AllPixelLengthButNotEnough_Correct()
+ {
+ // Arrange
+ var layout = new GridLayout(new RowDefinitions("100,200,300"));
+
+ // Action
+ var measure = layout.Measure(400);
+
+ // Assert
+ Assert.Equal(measure, new[] { 100d, 200d, 300d });
+ }
+ }
+}