How do you use RelativeLayout with controls that depend on their own size for their position?

c#, xamarin, xamarin.forms

Solution

Delving deep into the (current) implementation of `RelativeLayout`, I found a truth I did not expect: it does not consult a view's `GetSizeRequest()` method or call its `Layout()` method before the constraints are calculated, because those constraints might affect the control's final size. The consequence: while the constraints are being calculated, the control's bounds reflect its old position and size.

To "fix" this, call the view's GetSizeRequest() inside constraints that need the most up-to-date size of the control:

public class MainPage : ContentPage {
    public MainPage() {
        var layout = new RelativeLayout();

        var label = new Label() {
            Text = "I want to be right-aligned."
        };
        Func<RelativeLayout, double> getLabelWidth = (parent) => label.GetSizeRequest(parent.Width, parent.Height).Request.Width;
        layout.Children.Add(label,
            Constraint.RelativeToParent((rl) => rl.Width - getLabelWidth(rl)),
            Constraint.Constant(10));

        var button = new Button() {
            Text = "Invalidate"
        };
        button.Clicked += (object sender, EventArgs e) => layout.ForceLayout();
        layout.Children.Add(button,
            Constraint.Constant(10),
            Constraint.Constant(10));

        Content = layout;
    }
}

Problem

I'm having quite a few other problems with layout that require a lot of extra InvalidateLayout() calls, so I'm starting to question if I understand how RelativeLayout works. Here's a very simple example of a UI that wants a right-aligned label: ``` public class MainPage : ContentPage { public MainPage() { var layout = new RelativeLayout(); var label = new Label() { Text = "I want to be right-aligned." }; layout.Children.Add(label, Constraint.RelativeToParent((rl) => rl.Width - label.Width), Constraint.Constant(10)); var button = new Button() { Text = "Invalidate" }; button.Clicked += (object sender, EventArgs e) => layout.ForceLayout(); layout.Children.Add(button, Constraint.Constant(10), Constraint.Constant(10)); Content = layout; } } ``` I expect this to start with the label properly aligned, but it does not align the label correctly until another layout pass is forced. By overriding methods like OnSizeRequest() in my custom control, I've determined this is because the calls to OnSizeRequest() don't happen until after the calls to the RelativeLayout's constraint lambdas. So, when the page is laid out, the label's Width is -1. When ForceLayout() is called later, the Label has had a chance to perform its layout logic and has properly set the Width property, so it gets laid out correctly. In a larger context, I'm trying to make a button that, when clicked, fades out and a label slides into place where it was. It's to be aligned in the bottom-right corner of my layout, but I'm finding that modifying Opacity or IsVisible only inconsistently updates the layout. The only consistent behavior is RelativeLayout really likes to ask for the control's size before it gets a chance to resize itself. Am I interpreting how to use a RelativeLayout wrong, or is this a mistake in its logic?

Original source

Related problems