Hi,
I've got a complex layout, consisting of a horizontal stack layout containing a list of grids. Every grid has 3 rows - 1 with an image, 1 with a big label, and 1 with a small label. Here's the code that creates the layout:
for (int i = 0; i < sights.Count(); ++i)
{
var placeGrid = new Grid
{
BackgroundColor = Color.White,
Margin = 0,
RowSpacing = 0,
Padding = 0
};
var imageRow = new RowDefinition
{
Height = new GridLength(6, GridUnitType.Star)
};
var labelRow = new RowDefinition
{
Height = new GridLength(3, GridUnitType.Star)
};
var distanceRow = new RowDefinition()
{
Height = new GridLength(2, GridUnitType.Star)
};
placeGrid.RowDefinitions.Add(imageRow);
placeGrid.RowDefinitions.Add(labelRow);
placeGrid.RowDefinitions.Add(distanceRow);
var placePreview = new Image() { Source = ImageSource.FromResource("res.png"), HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.FillAndExpand, Margin = new Thickness(0, 5, 0, 2) };
var placeLabel = new Label() { Text = sights[i].Name, HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.FillAndExpand, HorizontalTextAlignment = TextAlignment.Center, Margin = new Thickness(5, 0, 5, 0), LineBreakMode = LineBreakMode.NoWrap };
FontCalc.SplitLabel(placeLabel);
FontCalc.FitLabelToBounds(placeLabel, AutoFit.Width);
Label distanceLabel = (userLocation != null) ?
new Label() { Text = MilesToKm(DistanceTo(new Coordinates(userLocation.Latitude, userLocation.Longitude), sights[i].GetCoordinates())).ToString("0.0") + " km", VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.CenterAndExpand, LineBreakMode = LineBreakMode.WordWrap, Margin = new Thickness(5, 0, 5, 5)}
: null;
placeGrid.Children.Add(placePreview, 0, 0);
placeGrid.Children.Add(placeLabel, 0, 1);
if(distanceLabel != null)
placeGrid.Children.Add(distanceLabel, 0, 2);
PlacesStackLayout.Children.Add(placeGrid);
if (i + 1 != Places.Count())
{
var separator = new BoxView
{
CornerRadius = 10,
Color = Color.LightGray,
WidthRequest = 3,
Opacity = 0.25,
Margin = 7,
VerticalOptions = LayoutOptions.Fill,
HorizontalOptions = LayoutOptions.Center
};
PlacesStackLayout.Children.Add(separator);
}
}
And here's the FontCalc class:
enum AutoFit {Width, Height}
struct FontCalc
{
public double FontSize { private set; get; }
public double TextBound { private set; get; }
public FontCalc(Label label, double fontSize, double containerBound, AutoFit calcType)
{
FontSize = fontSize;
label.FontSize = fontSize;
if(calcType == AutoFit.Width)
{
SizeRequest sizeRequest = label.Measure(Double.PositiveInfinity, containerBound, MeasureFlags.IncludeMargins);
TextBound = sizeRequest.Request.Width;
}
else //(calcType == FontCalcType.FitToHeight)
{
SizeRequest sizeRequest = label.Measure(containerBound, Double.PositiveInfinity, MeasureFlags.IncludeMargins);
TextBound = sizeRequest.Request.Height;
}
}
public static void SplitLabel(Label label)
{
if (label.Text == null)
return;
int index = 0;
int lastLineBreak = 0;
for (int i = 0; ; ++i)
{
lastLineBreak = index;
index = label.Text.IndexOf(' ', index + 1);
if (index == -1)
break;
if ((i + 1) % 2 == 0 && index - lastLineBreak > 4)
{
label.Text = label.Text.Remove(index, 1);
label.Text = label.Text.Insert(index, Environment.NewLine);
}
}
}
public static void FitLabelToBounds(Label label, AutoFit autoFit)
{
if (label.Width <= 0 || label.Height <= 0)
return;
FontCalc lowerFontCalc;
FontCalc upperFontCalc;
if (autoFit == AutoFit.Width)
{
lowerFontCalc = new FontCalc(label, 10, label.Height, AutoFit.Width);
upperFontCalc = new FontCalc(label, 100, label.Height, AutoFit.Width);
}
else
{
lowerFontCalc = new FontCalc(label, 10, label.Width, AutoFit.Height);
upperFontCalc = new FontCalc(label, 100, label.Width, AutoFit.Height);
}
while (upperFontCalc.FontSize - lowerFontCalc.FontSize > 1)
{
// Get the average font size of the upper and lower bounds.
double fontSize = (lowerFontCalc.FontSize + upperFontCalc.FontSize) / 2;
FontCalc newFontCalc = (autoFit == AutoFit.Width) ? new FontCalc(label, fontSize, label.Height, AutoFit.Width) : new FontCalc(label, fontSize, label.Width, AutoFit.Height);
if (newFontCalc.TextBound > ((autoFit == AutoFit.Width) ? label.Width : label.Height))
{
upperFontCalc = newFontCalc;
}
else
{
lowerFontCalc = newFontCalc;
}
}
label.FontSize = lowerFontCalc.FontSize;
}
}
There are 2 problems with the layout, which I can't solve:
1. The smaller label gets cut on smaller devices (below 4 inches)
2. The bigger label doesn't fit - the font is too big. My FitLabelToBounds method in FontCalc class doesn't seem to work properly in this case, although it works fine in another layout.
3. The labels are too far away from each other on bigger devices.
Here's how the layout looks on a small phone (3.7 inches):
And on a big tablet (10.1 inches):
How can I fix that?