RelativeLayout в вычислении ширины StackLayout

У меня есть странная проблема с RelativeLayouts, вложенным в StackLayout, кажется, что StackLayout вычисляет ширину своих элементов, должен использовать ширину RelativeLayout, а затем RelativeLayout снова вычисляет ее. Это приводит к тому, что дочернее управление представляет собой квадрат относительной ширины, но следующие элементы управления помещаются как по относительной ширине.

Является ли это ошибкой или я делаю что-то неправильно?

Пример

<?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>

Снимок экрана

Ответ 1

Два элемента RelativeLayout могут быть запутанным фактором, и тот факт, что каждый из них указан в позиции X относительно родительского StackLayout, который кажется конфликтом.

Из комментариев следует, что если каждый RelativeLayout должен быть половиной ширины родительского StackLayout, макет можно упростить, исключив один из элементов RelativeLayout.

Указанные ширины дочерних полос затем могут быть разделены на два так, чтобы ширина каждого была по назначению относительно родительского StackLayout, а положения X, установленные относительно родителя, также располагались горизонтально.

Вот что я придумал:

<?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>

Делает ли это нечто более близкое к ожидаемому?

Снимок экрана

Ответ 2

Ничто не позиционируется или не расширяется до любого из указанных значений.

Это не совсем так. Просто заметив это, красная полоса выглядит примерно так: соотношение ширины 7: 3 к первой синей полосе. Другими словами, это первый RelativeLayout. Второй RelativeLayout частично вытолкнут с экрана, но похоже, что второй ContentView полностью находится на экране и имеет ширину пропорционально двум первым полосам - т.е. зеленая полоса примерно равна соотношению 5: 7 к красной полосе.

Другими словами, два RelativeViews получили равные распределения пространства, а два ContentViews полностью находятся на экране. Однако общая ширина двух RelativeLayouts больше ширины экрана!

Вы можете посмотреть исходный код для StackLayout здесь: https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/StackLayout.cs

И RelativeLayout здесь: https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/RelativeLayout.cs.

Я не просмотрел все подробно, но для StackLayout есть макет с двумя проходами, сначала пытаясь сделать "наивный" макет, надеясь, что все дети подойдут. Если нет, он проходит через сжатие. А поскольку дети RelativeLayout не имеют определенной ширины, они сначала пытаются захватить всю ширину родителя и сжимаются оттуда.

В основном, это сводится к тому, что это недостоверная система с точки зрения Xamarin.Forms. Использование StackLayout подобно этому, где нет ничего, дающего определенную ширину от родителя или ребенка, может привести к неожиданным результатам. Для таких ситуаций, когда вы не хотите динамического макета, другие макеты, такие как Grid, дают гораздо более предсказуемые результаты.

Edit:

При дальнейшем исследовании, если у вас есть StackLayout с несколькими дочерними элементами, включая один (или более) RelativeLayout без спецификации ширины, вы, вероятно, ошибаетесь. Часть этого имеет смысл, но вы также видите симптомы сомнительного (IMO) дизайнерского решения.

RelativeLayout, по умолчанию, предполагает, что он имеет ту же ширину, что и его родитель. StackLayout имеет многоступенчатый процесс, сначала выполняет проход "Мера", затем "Пропуск макета" и, наконец, пропуск сжатия (который на самом деле не вступает в игру в этой ситуации). Во время измерения RelativeLayout определяет, сколько места ему нужно, чтобы выложить своих детей. Для первого RelativeLayout это означает, что ему требуется около 70% от общей ширины его родителя (т.е. 70% от общей ширины экрана - я игнорирую дробную сумму). Следующий RelativeLayout говорит, что ему требуется 50% от общей ширины.

Затем идет переход макета. Первый RelativeLayout получает 70% запросов, поэтому он определяет своего ребенка, который будет составлять 70% (соглашаясь с вашими наблюдениями). Второй RelativeLayout получает 50% запросов и выделяет 50% его ребенку. Да, это означает, что StackLayout выделяет более 100% ширины экрана. Конечно, он отображает только первые 100%.

Итак, способ, которым Xamarin.Forms делает макет, кажется немного странным, но я также не уверен в ограничениях, которые у вас есть на нужном вам решении.

Предполагая, что вам нужно иметь несколько родственных братьев RelativeLayout в StackLayout, в соответствии с этими инструкциями вы получите то, что хотите:

  • Заполните всю ширину каждого RelativeLayout, даже если это просто пустой ContentView. Если вы этого не сделаете, описанный выше шаг измерения/макета приведет к сильным головным болям.

  • Назначьте небольшое значение MinimumWidthRequest и HorizontalOptions, включая Expand для каждого RelativeLayout. Это позволит пропустить компресс (упомянутый выше) и дать каждому RelativeLayout такое же распределение ширины. При тестировании я использовал MinWidthRequest = "20" и HorizontalOptions = "StartAndExpand", и это сработало.

Выполнение этого даст достаточные ограничения для механизма компоновки Xamarin.Forms, чтобы вести себя так, как вы ожидаете.