Android9 Flexlayout 错误?

Android9 Flexlayout bug?

我在框架内有一个 flexlayout,一个网格和一个 stacklayout:

<CollectionView x:Name="collectionView"
            Margin="20"
            SelectionMode="Single"
            SelectionChanged="OnSelectionChanged">
    <CollectionView.ItemsLayout>
        <LinearItemsLayout Orientation="Vertical"
                       ItemSpacing="10" />
    </CollectionView.ItemsLayout>
    <!-- Define the appearance of each item in the list -->
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <StackLayout Margin="0,10,0,0" Spacing="0">
                <StackLayout Orientation="Horizontal" Margin="0">
                    <Label FontSize="Medium" FontAttributes="Bold" Text="{Binding Name, Mode=OneWay}" />
                    <Label Text="{Binding Bottle.ID, Mode=OneWay}" FontSize="Medium" 
                       FontAttributes="Italic" VerticalTextAlignment="Center" 
                       Margin="20,0,0,0"/>
                </StackLayout>

                <Frame CornerRadius="10" BorderColor="Black" IsVisible="{Binding IsConnected, Converter={StaticResource InverseBooleanConverter}, Mode=OneWay}" Padding="10">
                    <Label Text="not connected" FontSize="Small"/>
                </Frame>
                <Frame CornerRadius="10" BorderColor="Black" IsVisible="{Binding IsConnected, Mode=OneWay}" Padding="10">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="40" />
                        </Grid.ColumnDefinitions>

                        <StackLayout Grid.Column="0">
                            <FlexLayout  BindableLayout.ItemsSource="{Binding Bottle.Components, Mode=OneWay}" 
                                     Direction="Row" JustifyContent="Start" Wrap="Wrap" 
                                     AlignItems="Center" AlignContent="Start"
                                     >
                                <BindableLayout.ItemTemplate>
                                    <DataTemplate>
                                        <Label FontSize="Small" Text="{Binding DisplayString}" VerticalOptions="CenterAndExpand" Margin="0,0,10,0" Padding="0"/>
                                    </DataTemplate>
                                </BindableLayout.ItemTemplate>
                            </FlexLayout>
                            <StackLayout Orientation="Horizontal" IsVisible="{Binding IsConnected, Mode=OneWay}" VerticalOptions="FillAndExpand">
                                <Label FontSize="Small" Text="in " IsVisible="{Binding Bottle.IsCarrier, Converter={StaticResource InverseBooleanConverter}, Mode=OneWay}"/>
                                <Label FontSize="Small" Text="{Binding Bottle.CarrierGas, Mode=OneWay}"/>
                                <Label FontSize="Small" Text=" 100%" IsVisible="{Binding Bottle.IsCarrier, Mode=OneWay}" />
                            </StackLayout>

                        </StackLayout>

                        <Label FontSize="Small" Text="(ISO)" FontAttributes="Italic" Grid.Column="1" IsVisible="False">
                            <Label.Triggers>
                                <MultiTrigger TargetType="Label">
                                    <MultiTrigger.Conditions>
                                        <BindingCondition Binding="{Binding Bottle.IsISOCertified, Mode=OneWay}" Value="True" />
                                        <BindingCondition Binding="{Binding IsConnected, Mode=OneWay}" Value="True" />
                                    </MultiTrigger.Conditions>
                                    <Setter Property="IsVisible" Value="True" />
                                </MultiTrigger>
                            </Label.Triggers>
                        </Label>
                    </Grid>
                </Frame>
            </StackLayout>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

在特定的输入数据处,flexlayout 显示不正确(见列表中的第一个元素):

如果我删除框架,文本会正确显示:

如果我保留框架并将 flexlayout 元素的边距或填充增加一个,文本将正确显示为 2 行。

<DataTemplate>
    <Label FontSize="Small" Text="{Binding DisplayString}" VerticalOptions="CenterAndExpand" Margin="0,0,11,0"/>
</DataTemplate>

此外,如果我将字体大小增加到中号,它也能正确显示。另外,如果我不重新启动应用程序,而是让 visual studio 自动更新表单,在减小字体大小后,显示是正确的。但是,如果我重新启动 Android 模拟器,问题又会出现。

中号字体:

<DataTemplate>
    <Label FontSize="Medium" Text="{Binding DisplayString}" VerticalOptions="CenterAndExpand" Margin="0,0,10,0"/>
</DataTemplate>

将其重置为小但未重新启动应用程序后:

我想,增加 Margin/Padding 或增加字体大小或删除框架在长 运行 上无法正常工作,因为总会有一些 data/screen 大小打破布局的组合。

在我看来,布局仅在某些极端情况下失败。但是,当元素宽度(+边距)超过某个限制时,flexlayout 认为元素必须显示在 2 行中,然后布局就可以了。

这是xamarin/android的bug,还是我的代码有问题?谁能提出可靠的解决方法和解决方案?

一些额外的信息,不确定需要什么:

  • 目标 Android 版本:Android 9.0(API 28 级 - 派)
  • 模拟器:Pixel 2 Pie 9.0 - API28 1080x1920 420dpi

编辑:我还测试了 1020x1920、1000x1920、800x1920、1200x1920 显示尺寸,它们都出现了同样的问题。因此,要么 Android 设备管理器中的设置未被接管 (hw.lcd.width),要么问题与显示大小无关。

Edit2:在 Settings/Display 中更改显示大小会影响布局,如果我将其设置为更低或更大,则会修复它。但我猜它只是改变了标签的 FontSize 参数。


在测试@ToolmakerSteve 的解决方案时,我发现如果删除列表中的最后一项(NO 2100ppm),children 界限会更高。Width-s 会更高。然后我为每个 children 调用了 measure,我看到,当最后一个项目丢失时,宽度与 bound.Width-s 相匹配。所以我得到了这段代码:

//
class FlexLayoutImproveMeasure : FlexLayout
{
    protected override void LayoutChildren(double x, double y, double width, double height)
    {
        base.LayoutChildren(x, y, width, height);

        foreach (var child in Children)
        {
            Rectangle cb = child.Bounds;
            Size s = child.Measure(double.PositiveInfinity, cb.Height).Request;

            double newW = s.Width;

            var childBounds2 = new Rectangle(cb.X, cb.Y, newW, cb.Height);
            child.Layout(childBounds2);
        }
    }
}

我发现 FlexLayout 有时没有给每个标签足够的空间;标签被截断。

这是 FlexLayout 的子class,它为每个 Label 添加了少量宽度。

FlexLayoutImproveMeasure.cs:

using Xamarin.Forms;

namespace XFSOAnswers
{
    class FlexLayoutImproveMeasure : FlexLayout
    {
        protected override void LayoutChildren(double x, double y, double width, double height)
        {
            base.LayoutChildren(x, y, width, height);

            //var childBoundss = new List<Rectangle>();
            foreach (var child in Children)
            {
                Rectangle cb = child.Bounds;
                // Make each child slightly wider: labels were being truncated.
                // Less than 2 didn't always work. Increase as needed, up to right margin.
                const int extraW = 2;

                double newX = cb.X;
                // TBD whether part of the problem is label that isn't pixel-aligned.
                // (So far, seems more reliable to increase extraW instead.)
                //MAYBE double newX = Math.Floor(cb.X);

                double newW = cb.Width + extraW;
                // TBD whether it helps to ensure width extends to next pixel.
                // (So far, seems more reliable to increase extraW instead.)
                //MAYBE double newW = Math.Ceiling(cb.Width) + extraW;
                //double ceilingRight = Math.Ceiling(cb.X + cb.Width);
                //MAYBE double newW = ceilingRight - Math.Floor(cb.X) + extraW;

                var childBounds2 = new Rectangle(newX, cb.Y, newW, cb.Height);
                child.Layout(childBounds2);
                //childBoundss.Add(child.Bounds);
                //childBoundss.Add(cb);
            }
        }
    }
}

用这个代替 FlexLayout。这是通过向页面添加一个 xmlns: 声明来访问项目中的名称空间来完成的。这里的命名空间是 XFSOAnswers。更改 xmlns:local 行以引用您将上述 class 放入的任何名称空间。

XAML 个页面:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XFSOAnswers"
             x:Class="XFSOAnswers.FlexLayoutInItemTemplatePage">
    <ContentPage.Content>
        <StackLayout>
            <!-- labels truncated at width in range 188-194 -->
            <CollectionView ItemsSource="{Binding Items}"
                        WidthRequest="190" HorizontalOptions="Center"
                        Margin="20" >
                <CollectionView.ItemsLayout>
                    <LinearItemsLayout Orientation="Vertical" ItemSpacing="10" />
                </CollectionView.ItemsLayout>
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <Frame CornerRadius="10" BorderColor="Black"
                           BackgroundColor="LightBlue" Padding="10">
                            <local:FlexLayoutImproveMeasure
                                Wrap="Wrap"
                                BindableLayout.ItemsSource="{Binding Items2}"
                                BackgroundColor="LightGray" >
                                <BindableLayout.ItemTemplate>
                                    <DataTemplate>
                                        <Label FontSize="Small" Text="{Binding .}"
                                           VerticalOptions="Start" HorizontalOptions="Start" Margin="0,0,10,0" Padding="0"/>
                                    </DataTemplate>
                                </BindableLayout.ItemTemplate>
                            </local:FlexLayoutImproveMeasure>
                        </Frame>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

页面后面的代码有绑定:

using System.Collections.Generic;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace XFSOAnswers
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class FlexLayoutInItemTemplatePage : ContentPage
    {
        public FlexLayoutInItemTemplatePage()
        {
            InitializeComponent();
            BindingContext = this;
        }

        public List<ListModel> Items { get; } = new List<ListModel> {
            new ListModel(),
            new ListModel(),
        };
    }


    public class ListModel
    {
        public List<string> Items2 { get; } = new List<string> {
            "aaa", "bbbbbbbbb", "cc", "dddd", "e",
        };
    }
}

Items2 选择的字符串使得在 WidthRequest=190 时,“dddd”几乎不适合第一行。使用和不使用最后的“e”进行测试,以了解 FlexLayout 在这两种情况下的作用。


原始 FlexLayout,截断标签:


将 FlexLayoutImproveMeasure 与 extraW = 2 结合使用: