Xamarin.Forms 中的可绑定布局

通过可绑定布局,派生自 Layout<T> 类的任何布局类可通过绑定到项集合来生成其内容,并可以选择使用 DataTemplate 设置每个项的外观。 可绑定布局由 BindableLayout 类提供,该类公开以下附加属性:

  • ItemsSource – 指定要由布局显示的 IEnumerable 项的集合。
  • ItemTemplate – 指定要应用于由布局显示的项集合中每个项的 DataTemplate
  • ItemTemplateSelector – 指定要用于在运行时为项选择 DataTemplateDataTemplateSelector

注意

同时设置 ItemTemplateItemTemplateSelector 属性时,ItemTemplate 属性将优先。

此外,BindableLayout 类还公开以下可绑定属性:

  • EmptyView – 指定当 ItemsSource 属性为 null,或 ItemsSource 属性指定的集合为 null 或为空时将显示的 string 或视图。 默认值为 null
  • EmptyViewTemplate – 指定当 ItemsSource 属性为 null,或 ItemsSource 属性指定的集合为 null 或为空时将显示的 DataTemplate。 默认值为 null

注意

同时设置 EmptyViewEmptyViewTemplate 属性时,EmptyViewTemplate 属性将优先。

所有这些属性都可以附加到所有派生自 Layout<T> 类的 AbsoluteLayoutFlexLayoutGridRelativeLayoutStackLayout 类。

Layout<T> 类公开一个 Children 集合,向其添加了布局的子元素。 在 BindableLayout.ItemsSource 属性设置为项集合并附加到 Layout<T> 派生的类时,集合中的每个项都会添加到 Layout<T>.Children 集合中供布局显示。 然后,Layout<T> 派生类将在基础集合发生更改时更新其子视图。 有关 Xamarin.Forms 布局周期的详细信息,请参阅创建自定义布局

仅当要显示的项集合较小且不需要滚动和选择时,才应使用可绑定布局。 虽然可以通过将可绑定布局包装在 ScrollView 中来提供滚动,但不建议这样做,因为可绑定布局缺乏 UI 虚拟化。 需要滚动时,应使用包含 UI 虚拟化的可滚动视图,例如 ListViewCollectionView。 未采纳此建议可能会导致性能问题。

重要

虽然从技术上讲,可以将可绑定布局附加到派生自 Layout<T> 类的任何布局类,但这样做并不总是可行,尤其是对于 AbsoluteLayoutGridRelativeLayout 类。 例如,考虑要使用可绑定布局在 Grid 中显示数据集合的方案,其中集合中的每个项都是包含多个属性的对象。 Grid 中的每一行都应显示集合中的一个对象,其中,Grid 中的每一列都显示该对象的一个属性。 由于可绑定布局的 DataTemplate 只能包含单个对象,因此该对象必须是包含多个视图的布局类,每个视图在特定 Grid 列中显示对象的其中一个属性。 虽然可以使用可绑定布局实现此方案,但会导致父级 Grid 包含绑定集合中每个项的子级 Grid,以这种方式使用 Grid 布局效率极其低下,并且存在问题。

使用数据填充可绑定布局

可绑定布局通过将其 ItemsSource 属性设置为实现 IEnumerable 的任何集合,并将其附加到 Layout<T> 派生类来填充数据:

<Grid BindableLayout.ItemsSource="{Binding Items}" />

等效 C# 代码如下:

IEnumerable<string> items = ...;
var grid = new Grid();
BindableLayout.SetItemsSource(grid, items);

如果在布局上已设置 BindableLayout.ItemsSource 附加属性但未设置 BindableLayout.ItemTemplate 附加属性,则 IEnumerable 集合中的每个项都将由 BindableLayout 类创建的 Label 来显示。

定义项外观

可以通过将 BindableLayout.ItemTemplate 附加属性设置为 DataTemplate 来定义可绑定布局中每个项的外观:

<StackLayout BindableLayout.ItemsSource="{Binding User.TopFollowers}"
             Orientation="Horizontal"
             ...>
    <BindableLayout.ItemTemplate>
        <DataTemplate>
            <controls:CircleImage Source="{Binding}"
                                  Aspect="AspectFill"
                                  WidthRequest="44"
                                  HeightRequest="44"
                                  ... />
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</StackLayout>

等效 C# 代码如下:

DataTemplate circleImageTemplate = ...;
var stackLayout = new StackLayout();
BindableLayout.SetItemsSource(stackLayout, viewModel.User.TopFollowers);
BindableLayout.SetItemTemplate(stackLayout, circleImageTemplate);

在此示例中,TopFollowers 集合中的每个项将由 DataTemplate 中定义的 CircleImage 视图显示:

具有 DataTemplate 的可绑定布局

有关数据模板的详细信息,请参阅 Xamarin.Forms 数据模板

在运行时选择项外观

可以通过将 BindableLayout.ItemTemplateSelector 附加属性设置为 DataTemplateSelector,在运行时根据项值选择可绑定布局中每个项的外观:

<FlexLayout BindableLayout.ItemsSource="{Binding User.FavoriteTech}"
            BindableLayout.ItemTemplateSelector="{StaticResource TechItemTemplateSelector}"
            ... />

等效 C# 代码如下:

DataTemplateSelector dataTemplateSelector = new TechItemTemplateSelector { ... };
var flexLayout = new FlexLayout();
BindableLayout.SetItemsSource(flexLayout, viewModel.User.FavoriteTech);
BindableLayout.SetItemTemplateSelector(flexLayout, dataTemplateSelector);

以下示例显示了示例应用程序中使用的 DataTemplateSelector

public class TechItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate DefaultTemplate { get; set; }
    public DataTemplate XamarinFormsTemplate { get; set; }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        return (string)item == "Xamarin.Forms" ? XamarinFormsTemplate : DefaultTemplate;
    }
}

TechItemTemplateSelector 类定义设置为不同数据模板的 DefaultTemplateXamarinFormsTemplateDataTemplate 属性。 当项等于“Xamarin.Forms”时,OnSelectTemplate 方法返回 XamarinFormsTemplate,它以暗红色显示项,项旁边有一颗心。 当项不等于“Xamarin.Forms”时,OnSelectTemplate 方法返回 DefaultTemplate,它使用 Label 的默认颜色显示项:

具有 DataTemplateSelector 的可绑定布局

有关数据模板选择器的详细信息,请参阅创建 Xamarin.Forms DataTemplateSelector

当数据不可用时显示字符串

可以将 EmptyView 属性设置为字符串,当 ItemsSource 属性为 nullItemsSource 属性指定的集合为 null 或为空时,Label 将显示该字符串。 以下 XAML 显示了此方案的示例:

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}"
             BindableLayout.EmptyView="No achievements">
    ...
</StackLayout>

结果是,当数据绑定集合为 null 时,将显示设置为 EmptyView 属性值的字符串:

iOS 和 Android 上可绑定布局字符串空视图的屏幕截图

当数据不可用时显示视图

EmptyView 属性可设置为视图,当 ItemsSource 属性为 null 时,或 ItemsSource 属性指定的集合为 null 或为空时,将显示该视图。 这可以是单个视图,也可以是包含多个子视图的视图。 以下 XAML 示例显示设置为包含多个子视图的视图的 EmptyView 属性:

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
    <BindableLayout.EmptyView>
        <StackLayout>
            <Label Text="None."
                   FontAttributes="Italic"
                   FontSize="{StaticResource smallTextSize}" />
            <Label Text="Try harder and return later?"
                   FontAttributes="Italic"
                   FontSize="{StaticResource smallTextSize}" />
        </StackLayout>
    </BindableLayout.EmptyView>
    ...
</StackLayout>

结果是,当数据绑定集合为 null 时,将显示 StackLayout 及其子视图。

iOS 和 Android 上具有多个视图的可绑定布局空视图的屏幕截图

同样,可以将 EmptyViewTemplate 设置为 DataTemplate,当 ItemsSource 属性为 nullItemsSource 属性指定的集合为 null 或为空时,将显示该模板。 DataTemplate 可以包含单个视图,也可以包含有多个子视图的视图。 此外,将从 BindableLayoutBindingContext 继承 EmptyViewTemplateBindingContext。 以下 XAML 示例显示设置为包含单个视图的 DataTemplateEmptyViewTemplate 属性:

<StackLayout BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
    <BindableLayout.EmptyViewTemplate>
        <DataTemplate>
            <Label Text="{Binding Source={x:Reference usernameLabel}, Path=Text, StringFormat='{0} has no achievements.'}" />
        </DataTemplate>
    </BindableLayout.EmptyViewTemplate>
    ...
</StackLayout>

结果是,当数据绑定集合为 null 时,将显示 DataTemplate 中的 Label:

iOS 和 Android 上可绑定布局空视图模板的屏幕截图

注意

不能通过 DataTemplateSelector 设置 EmptyViewTemplate 属性。

在运行时选择 EmptyView

当数据不可用时将以 EmptyView 显示的视图,可在 ResourceDictionary 中定义为 ContentView 对象。 然后,可以在运行时根据某些业务逻辑将 EmptyView 属性设置为特定的 ContentView。 以下 XAML 显示了此方案的示例:

<ContentPage ...>
    <ContentPage.Resources>
        ...    
        <ContentView x:Key="BasicEmptyView">
            <StackLayout>
                <Label Text="No achievements."
                       FontSize="14" />
            </StackLayout>
        </ContentView>
        <ContentView x:Key="AdvancedEmptyView">
            <StackLayout>
                <Label Text="None."
                       FontAttributes="Italic"
                       FontSize="14" />
                <Label Text="Try harder and return later?"
                       FontAttributes="Italic"
                       FontSize="14" />
            </StackLayout>
        </ContentView>
    </ContentPage.Resources>

    <StackLayout>
        ...
        <Switch Toggled="OnEmptyViewSwitchToggled" />

        <StackLayout x:Name="stackLayout"
                     BindableLayout.ItemsSource="{Binding UserWithoutAchievements.Achievements}">
            ...
        </StackLayout>
    </StackLayout>
</ContentPage>

XAML 在页面级 ResourceDictionary 中定义两个 ContentView 对象,其中 Switch 对象控制将哪个 ContentView 对象设置为 EmptyView 属性值。 切换 Switch 时,OnEmptyViewSwitchToggled 事件处理程序将执行 ToggleEmptyView 方法:

void ToggleEmptyView(bool isToggled)
{
    object view = isToggled ? Resources["BasicEmptyView"] : Resources["AdvancedEmptyView"];
    BindableLayout.SetEmptyView(stackLayout, view);
}

ToggleEmptyView 方法根据 Switch.IsToggled 属性的值,将 stackLayout 对象的 EmptyView 属性设置为 ResourceDictionary 中存储的两个 ContentView 对象之一。 然后,当数据绑定集合为 null 时,将显示设置为 EmptyView 属性的 ContentView 对象:

iOS 和 Android 上运行时空视图选择的屏幕截图