If you've done much Xamarin iOS work, you've probably run into Frank Krueger's awesome framework, EasyLayout
, that makes manually coded auto layouts considerably easier to read and maintain.
If you've ever wanted the same type of functionality for Xamarin Android either for consistency or ease of cross platform code sharing, now you can with EasyLayout.Droid.
What Is EasyLayout?
The original EasyLayout
takes Xamarin iOS code like this:
_passwordField.AddConstraint(NSLayoutConstraint.Create(
_passwordField, NSLayoutAttribute.Top, NSLayoutRelation.Equal,
_usernameTextField, NSLayoutAttribute.Bottom, 1f, 20f));
_passwordField.AddConstraint(NSLayoutConstraint.Create(
_passwordField, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal,
View, NSLayoutAttribute.CenterX, 1f, 0f));
And turns it into this:
View.ConstrainLayout(() =>
_passwordField.Frame.Top == _usernameTextField.Frame.Bottom + 20 &&
_passwordField.Frame.GetCenterX() == View.Frame.GetCenterX()
);
If you're on a team, or storyboards just aren't your thing, it's a lifesaver!
What's Wrong with Android .axml?
Android's axml files are ok, but on large projects, they take a long time to generate, and they make it hard to share layout information cross platform. But if you try to code Android by hand, you quickly discover the same type of verbosity that Xamarin iOS had. Enter EasyLayout.Droid
.
Example 1 - Parent Align
If you want to align an image to the edges of the frame, you used to do this:
var layoutParams = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MatchParent,
ViewGroup.LayoutParams.MatchParent);
layoutParams.AddRule(LayoutRules.AlignParentTop);
layoutParams.AddRule(LayoutRules.AlignParentBottom);
layoutParams.AddRule(LayoutRules.AlignParentRight);
layoutParams.AddRule(LayoutRules.AlignParentLeft);
_image.LayoutParams = layoutParams;
Now you can do this:
relativeLayout.ConstrainLayout(() =>
_image.Top == relativeLayout.Top
&& _image.Right == relativeLayout.Right
&& _image.Left == relativeLayout.Left
&& _image.Bottom == relativeLayout.Bottom
);
There's no need to set LayoutParams
at all. If they don't exist, EasyLayout.Droid
will add them, if they do, EasyLayout.Droid
will append to them. And if you don't add them, it will take care of choosing LayoutParams.MatchParent
or WrapContent
.
Example 2 - Relative Alignment and Constants
If you wanted to align an image 20 dp under another image and center align it to the parent, you used to do this:
var layoutParams = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WrapContent,
ViewGroup.LayoutParams.WrapContent);
layoutParams.AddRule(LayoutRules.CenterHorizontal);
layoutParams.AddRule(LayoutRules.AlignBottom, image1.Id);
layoutParams.TopMargin = DpToPx(20);
_image2.LayoutParams = layoutParams;
There's a couple of gotchas.
- If you set the
TopMargin
to 20
, then Android assumes you mean pixels not device independent pixels. To fix that, you need to remember to call a function like DpToPx()
. - Your relative view (
image1
) needs to have an Id. If you forget to set it, there's no error, it just does strange layout things.
EasyLayout.Droid
replaces the code above with:
relativeLayout.ConstrainLayout(() =>
_image2.Top == _image1.Bottom + 20
&& _image2.GetCenterX() == relativeLayout.GetCenterX()
);
That's less code, and it's easier to read, plus there's some other small benefits:
- If you forget to add an Id to
_image1
, EasyLayout.Droid
will throw a helpful runtime error. EasyLayout.Droid
always assumes every number is in Dp, so it automatically converts all literals for you.
Incidentally, GetCenterX()
is one of a couple of new extension methods along with GetCenterY()
and GetCenter()
.
Example 3 - Constants
Constants weren't difficult to work with previously, but for completeness, they used to work like this:
var layoutParams = new RelativeLayout.LayoutParams(
DpToPx(50),
DpToPx(ViewModel.SomeHeight);
_image2.LayoutParams = layoutParams;
With EasyLayout.Droid
, you can do this:
relativeLayout.ConstrainLayout(() =>
_image.Width == 50
&& _image.Height == ViewModel.SomeHeight.ToConst()
);
As mentioned previously, 50 will be assumed to be in dp and will be auto-converted to pixels. Also arbitrary properties such as SomeHeight
will need the .ToConst()
extension method applied in order to tell EasyLayout.Droid
that they should be treated as constants.
Limitations
Android relative layouts are far from a replacement for iOS's auto layout. To that end, you cannot do the following operations that EasyLayout
could:
- Constraining Heights or Widths to be equal to the Heights or Widths of other elements
- Using
>=
or <=
signs to indicate GreaterThanOrEqual
or LessThanOrEqual
type constraints - Multiplication of elements (e.g.
_image2.Width == _image1.Width * .25f
)
Installation
If you want to add this to your project, you can either install via NuGet (safer):
Install-Package EasyLayout.Droid
or if you think it's perfect as-is (you don't want updates), you can copy EasyLayoutDroid.cs into your source. Next using EasyLayout.Droid
and you're good to go.
Conclusion
Hope this helps make someone's Xamarin Android day a little better. The code is MIT licensed. If you have any questions, please contact me on twitter.