A Xamarin grid helper layout that uses C# operator override.
The fork by t9mike adds: TODO
Bugs are likely mine. Please don't blame aloisdeniel. I am so grateful that he published his well thought out and wonderful work. Grids and stacks are so much easier to layout a UI with than iOS constraint-based approach. Thank you aloisdeniel!
I plan to add more samples showing my new features once I am farther along with using GridView for my project.
The auto grid sizing algorithm when there are % sized rows or columns is very crude. The size of the view in the %-based cell is used to define the overall size for the row or column. For example, if a grid spec was:
var time = Make_Box(100, 50, UIColor.Blue); // 100w x 50h
var p = Make_Box(50, 15, UIColor.Yellow); // 50w x 15h
var m = Make_Box(50, 15, UIColor.Red); // 50w x 15h
var layout = new Grid.Layout()
.WithRows(0.5f, 0.5f)
.WithColumns(-1, -1)
+ time.At(0, 0).RowSpan(2)
+ p.At(0, 1).Vertically(Layout.Alignment.Start)
+ m.At(1, 1).Vertically(Layout.Alignment.End);
This is a grid like:
+----------+-----+
| | p |
| time +-----+
| | m |
+----------+-----+
Then we could summize that the grid height should be 50 based on the 100wx50h. And that is achieved because the p and m cells only have 15 high height. But if they were instead each 30 high, the simple grid sizer algorithm in TODO would set the grid height to 60, when it should probably keep it 50.
WPF grid reference code for Grid would be a good start to a new sizing algo. See MeasureOverride().
Let's start with a common example with a simple layout that changes while in landscape.
var red = new UIView { BackgroundColor = UIColor.Red };
var blue = new UIView { BackgroundColor = UIColor.Blue, Frame = new CGRect(0,0,50,50) };
var cyan = new UIView { BackgroundColor = UIColor.Cyan };
var yellow = new UIView { BackgroundColor = UIColor.Yellow };
var portrait = new Grid.Layout()
{
Spacing = 10,
Padding = new Grid.Insets(10)
}
.WithRows(0.75f, 0.25f, 200f)
.WithColumns(0.75f, 0.25f)
+ red.At(0, 0).Span(2, 1)
+ blue.At(2, 0).Span(1, 2)
+ cyan.At(0, 1)
+ yellow.At(1,1);
var landscape = new Grid.Layout()
{
Spacing = 20,
Padding = new Grid.Insets(20),
}
.WithRows(1.00f)
.WithColumns(0.50f, 0.25f, 0.25f)
+ red.At(0, 0)
+ blue.At(0, 1)
.Vertically(Grid.Layout.Alignment.End)
.Horizontally(Grid.Layout.Alignment.Center)
+ cyan.At(0, 2);
var grid = new Grid();
grid.AddLayout(portrait);
grid.AddLayout(landscape, (g) => (g.Frame.Width > g.Frame.Height));
this.View = grid;
The getting started example should be self-explanatory but find here a more in-depth presentation.
A grid is the root UIView
that displays its subviews thanks to its current layout. Each of its layout has a Trigger
property : the grid will choose the first declared layout that match when update.
By default, the layout update is triggered when the grid size change, but you can force a new layout (for example if your triggers depends on other factors) by calling UpdateLayout()
.
A layout contains row and column definitions (number, size), but also the position of each subview (Cell
).
A global padding space inside your grid could be defined.
Example:
// left, top, right, bottom
layout.Padding = new Grid.Insets(10,20,10,5);
The spacing property defines the spacing between each row or column.
Example:
layout.Spacing = 10;
To define the columns and rows, use the WithRows/WithColumns(params float[] rows)
method. Each entry adds a row/column with the value as size.
If size > 1
, the value is considered as an absolute size.
If size <= 1
, the value will use the amount of the remaining space (after removing other absolute row/column sizes and spacings).
Example:
layout.WithRows(0.5f,100.0f,0.25f,0.25f);
For adding cells, extension methods are available for UIView
.
Must be the first declaration and indicates the top-left position of the cell.
Indicates the size of the cell (default : (1,1)
).
Indicates the vertical position of the view in the cell (default : Stretched
).
Indicates the horizontal position of the view in the cell (default : Stretched
).
Example:
var cell = subview.At(0, 1)
.Span(1,2)
.Vertically(Grid.Layout.Alignment.End)
.Horizontally(Grid.Layout.Alignment.Center)
The easiest and recommanded way is to use the +
operator override onto your layout to append new cells.
Example:
layout = layout + cell1 + cell2;
To add your layout to your grid, use the Grid.AddLayout(Grid.Layout layout)
method.
Example:
grid.AddLayout(layout)
The current selected layout of your grid can dynamically changed if you have more than one layout. In this case, you have to associate triggers to each other layout, to allow the view to be able to choose the right layout at a given time.
To achieve this, add the other layouts with the Grid.AddLayout(Grid.Layout layout, Func<Grid,bool> trigger)
method.
Example:
grid.AddLayout(layout2, (grid) => grid.Frame.Width > grid.Frame.Height);
Simply use a spacing of 0
and add empty rows/columns with a fixed size.
This layout system is vastly inspired by Windows (WPF, WinRT) and Xamarin.Forms Grid components.
You might be wondering why I'm doing this while there's already existing layout mecanisms :
- IMO
UIStackView
, contraints, autoresizing masks are too verbose for creating common layouts from code, in particular with purely dynamic layouts. I'm not a real fan of Storyboard constraints too to be honest. Just wonder of the number of lines of code/storyboard declarations needed to implement the simple getting started example that takes just a few lines of code ... - I also wanted to have fluent apis and to take advantage of C# operator override to have concise layout declarations.
- Having a simple unified layout API on all platforms is also a motivation.
- - [ ] Android Implementation
- Optimize and benchmarking
Contributions are welcome! If you find a bug please report it and if you want a feature please report it.
If you want to contribute code please file an issue and create a branch off of the current dev branch and file a pull request.
MIT © Aloïs Deniel