Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RelativeLayout in StackLayout Width Calculation

I've got an odd problem with RelativeLayouts nested in a StackLayout, it appears that the StackLayout calculates the width of its elements should be using the RelativeLayout width and then the RelativeLayout recalculates it again afterwards. This results in the child control being the relative width squared, but the following controls being placed as at relative width.

Is this a bug or am I doing something wrong?

example

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="MyClass"
             xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             >

  <StackLayout Orientation="Horizontal" BackgroundColor="Blue">
    <RelativeLayout>
      <ContentView BackgroundColor="Red"

          RelativeLayout.XConstraint=
                "{ConstraintExpression Type=RelativeToParent,
                                      Property=Width,
                                      Factor=0}"
          RelativeLayout.WidthConstraint=
                "{ConstraintExpression Type=RelativeToParent,
                                      Property=Width,
                                      Factor=0.707106}"
          RelativeLayout.YConstraint=
                  "{ConstraintExpression Type=RelativeToParent,
                                        Property=Height,
                                        Factor=0}"
          RelativeLayout.HeightConstraint=
                "{ConstraintExpression Type=RelativeToParent,
                                      Property=Height,
                                      Factor=1}">

      </ContentView>

    </RelativeLayout>
    <RelativeLayout>
      <ContentView BackgroundColor="Green"

          RelativeLayout.XConstraint=
                "{ConstraintExpression Type=RelativeToParent,
                                      Property=Width,
                                      Factor=0}"
          RelativeLayout.WidthConstraint=
                "{ConstraintExpression Type=RelativeToParent,
                                      Property=Width,
                                      Factor=0.5}"
          RelativeLayout.YConstraint=
                  "{ConstraintExpression Type=RelativeToParent,
                                        Property=Height,
                                        Factor=0}"
          RelativeLayout.HeightConstraint=
                "{ConstraintExpression Type=RelativeToParent,
                                      Property=Height,
                                      Factor=1}">

      </ContentView>

    </RelativeLayout>

  </StackLayout>
</ContentPage>

Screen shot

like image 993
Ewan Avatar asked Feb 08 '16 09:02

Ewan


People also ask

What are two types of values used in RelativeLayout?

Within a RelativeLayout , the position and size of children are specified as constraints using absolute values or relative values.

What is StackLayout?

A StackLayout is a layout that organizes its children in a one-dimensional stack, either horizontally or vertically. By default, a StackLayout is oriented vertically. Visual Studio.

How many types of layout in Xamarin forms?

There are three choices for GridUnitType sizes: Absolute , Auto , and Star . To determine the sizes of each column, the Grid starts by resolving the Absolute-size and Auto-size columns.


2 Answers

The two RelativeLayout elements could be a confusing factor, and the fact that each is specified to be at X position 0 relative to the parent StackLayout, which seems like a conflict.

It seems from the comments that if each RelativeLayout is expected to be half the width of the parent StackLayout, the layout could be simplified by eliminating one of the RelativeLayout elements.

The specified widths of the child strips could then be divided by two so that the width of each is as intended relative to the parent StackLayout, and the X positions set relative to the parent as well to position them horizontally.

Here is what I came up with:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="App1.HomePage"
         xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
>

<StackLayout Orientation="Horizontal" BackgroundColor="Blue">
  <RelativeLayout>
    <ContentView BackgroundColor="Red"
        RelativeLayout.XConstraint=
          "{ConstraintExpression Type=RelativeToParent,
                                 Property=Width,
                                 Factor=0}"
        RelativeLayout.WidthConstraint=
          "{ConstraintExpression Type=RelativeToParent,
                                 Property=Width,
                                 Factor=0.35}"
        RelativeLayout.YConstraint=
          "{ConstraintExpression Type=RelativeToParent,
                                 Property=Height,
                                 Factor=0}"
        RelativeLayout.HeightConstraint=
          "{ConstraintExpression Type=RelativeToParent,
                                 Property=Height,
                                 Factor=1}">
    </ContentView>
<!--
</RelativeLayout>
<RelativeLayout>
-->
    <ContentView BackgroundColor="Green"
        RelativeLayout.XConstraint=
          "{ConstraintExpression Type=RelativeToParent,
                                 Property=Width,
                                 Factor=0.5}"
        RelativeLayout.WidthConstraint=
          "{ConstraintExpression Type=RelativeToParent,
                                 Property=Width,
                                 Factor=0.25}"
        RelativeLayout.YConstraint=
          "{ConstraintExpression Type=RelativeToParent,
                                 Property=Height,
                                 Factor=0}"
        RelativeLayout.HeightConstraint=
          "{ConstraintExpression Type=RelativeToParent,
                                 Property=Height,
                                 Factor=1}">
      </ContentView>
    </RelativeLayout>
  </StackLayout>
</ContentPage>

Does this produce something closer to what is expected?

Screenshot

like image 110
Mark Larter Avatar answered Oct 21 '22 01:10

Mark Larter


Nothing is positioned or widthed to any of the values specified.

That isn't quite true. Just eyeballing it, the red band looks to be in about a 7:3 width ratio to the first blue band. In other words, that is the first RelativeLayout. The second RelativeLayout is partially pushed off the screen, but it does look like second ContentView is entirely on the screen, and widthed in proportion to the first two bands -- i.e. the green band is in approximately a 5:7 ratio to the red band.

In other words, the two RelativeViews did get equal allocations of space, and the two ContentViews are entirely on the screen. However the total width of the two RelativeLayouts is greater than the width of the screen!

You can look at the source code for StackLayout here: https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/StackLayout.cs

And RelativeLayout here: https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/RelativeLayout.cs.

I haven't gone through everything in detail, but there is a two pass layout for StackLayout, first trying to do a "naïve" layout, hoping all the children fit. If not, it goes through a compression pass. And since the RelativeLayout children don't have any width specified, they initially try to grab the entire width of the parent, and get compressed from there.

Basically what it comes down to is that this is an underconstrained system from the Xamarin.Forms perspective. Using StackLayout like this, where there is nothing giving a specific width from a parent or a child, can result in surprising results. For situations like this, where you don't want a dynamic layout, other layouts, like Grid, give much more predictable results.

Edit:

Upon further investigation, if you have a StackLayout with multiple children, including one (or more) RelativeLayout without a width specification, you're probably doing it wrong. Part of it makes sense, but you're also seeing the symptoms of a questionable (IMO) design decision.

RelativeLayout, by default, assumes that it is the same width as its parent. StackLayout has a multi-stage process, first doing a Measure pass, then a Layout pass, and finally a compress pass (which actually doesn't come into play in this situation). During Measure, the RelativeLayout figures out how much space it needs to actually lay out its children. For the first RelativeLayout, that means it needs about 70% of the total width of its parent (i.e. 70% of the total screen width -- I'm ignoring the fractional amount). The next RelativeLayout says that it needs 50% of the total width.

Then comes the layout pass. The first RelativeLayout is given the 70% it requested, so it lays out its child, which will be 70% of that (agreeing with your observations). The second RelativeLayout is given the 50% it asked for, and allocates 50% of it to its child. Yes, this means that StackLayout is allocating more than 100% of the screen width. Of course it will only display the first 100%.

So the way that Xamarin.Forms does the layout seems a little odd, but I'm also not sure on the constraints you have on the solution you need.

Assuming you need to have multiple RelativeLayout siblings under a StackLayout, following these instructions will get you what you want:

  1. Fill the full width of each RelativeLayout, even if it's just an empty ContentView. If you don't, the Measure/Layout pass described above is going to cause severe headaches.

  2. Assign a small MinimumWidthRequest and a HorizontalOptions including Expand to each RelativeLayout. This will allow the compress pass (mentioned above) to kick in, and give each RelativeLayout the same width allocation. In testing, I used MinimumWidthRequest="20" and HorizontalOptions="StartAndExpand" and that worked.

Doing this will provide enough constraints for the Xamarin.Forms layout engine to behave in the way you expect.

like image 30
DavidS Avatar answered Oct 21 '22 01:10

DavidS