可以包装 Listview 内集合内容的布局

A layout which can wrap contents of a collection which is inside a Listview

我正在尝试实施在部门工作的员工列表。有几个部门,一个部门有几个员工。以下是我的代码,我很难滚动和包装内容(员工图像和姓名)。至于包装内容,如果一行不够space,我希望内容(图像和员工的名字)换行显示。

到目前为止,我已经尝试了多种选择但都无济于事。我正在使用 ItemsControl I also tried adding a StackLayout instead of the WrapLayout

谁能告诉我如何解决滚动问题和内容环绕问题?是否有任何解决方法或我可以使用的任何其他布局?谢谢。

XAML

<ListView ItemsSource="{Binding Departments}" HasUnevenRows="True">
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <StackLayout Margin="20,20,20,20">
                        <Label Text="{Binding DepartmentName}" />
                        <Label Text="{Binding DepartmentId}" />

                        <local:ItemsControl ItemsSource="{Binding Employees}">
                            <local:ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Grid>
                                            <local:WrapLayout>
                                                <Image Source="{Binding ImageUrl}"
                                                       WidthRequest="60"
                                                       HeightRequest="60"/>
                                                <Label
                                                       Text="{Binding FirstName}"
                                                       HorizontalTextAlignment="Center"
                                                       VerticalTextAlignment="Center"
                                                    LineBreakMode="WordWrap"/>
                                            </local:WrapLayout>
                                    </Grid>
                                </DataTemplate>
                            </local:ItemsControl.ItemTemplate>
                        </local:ItemsControl>

                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>  


更新 1:

Department.cs

public class Department
{
    public int DepartmentID { get; set; }
    public string DepartmentName { get; set; }
    // and several other properties

    public List<Employee> Employees { get; set; }
}

Employee.cs

public class Employee
{
    public int EmployeeID { get; set; }
    public string FirstName { get; set; }
    public string ImageUrl{ get; set; }

    // and several other properties
}  

更新 2

似乎滚动并没有发生在我的一个测试设备上。但仍然需要一个能够包装控件的布局。

更新 3
我希望数据显示如下图所示。但是现在,使用上面的代码, WrapLayout 中的内容没有换行,而是调整了大小以适合一行。如果第一行没有 space,我希望它们被包裹起来。

当你提到 WrapLayout 时,我假设你指的是 this one as defined here

此外,由于您已经在使用 ItemsControl,我建议对其进行调整以支持 WrapLayout 而不是 StackLayout。例如:

public class ItemsControl : WrapLayout
{
    public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
        "ItemsSource", typeof(IList), typeof(ItemsControl), propertyChanging: OnItemsSourceChanged);

    /// <summary>
    /// Gets or sets the items source - can be any collection of elements.
    /// </summary>
    /// <value>The items source.</value>
    public IList ItemsSource
    {
        get { return (IList)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
        "ItemTemplate", typeof(DataTemplate), typeof(ItemsControl));

    /// <summary>
    /// Gets or sets the item template used to generate the visuals for a single item.
    /// </summary>
    /// <value>The item template.</value>
    public DataTemplate ItemTemplate
    {
        get { return (DataTemplate)GetValue(ItemTemplateProperty); }
        set { SetValue(ItemTemplateProperty, value); }
    }

    public ItemsControl()
    {
        Padding = new Thickness(0);
        Margin = new Thickness(0);
    }

    static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((ItemsControl)bindable).OnItemsSourceChangedImpl((IList)oldValue, (IList)newValue);
    }

    void OnItemsSourceChangedImpl(IList oldValue, IList newValue)
    {
        // Unsubscribe from the old collection
        if (oldValue != null)
        {
            INotifyCollectionChanged ncc = oldValue as INotifyCollectionChanged;
            if (ncc != null)
                ncc.CollectionChanged -= OnCollectionChanged;
        }

        if (newValue == null)
        {
            Children.Clear();
        }
        else
        {
            FillContainer(newValue);
            INotifyCollectionChanged ncc = newValue as INotifyCollectionChanged;
            if (ncc != null)
                ncc.CollectionChanged += OnCollectionChanged;
        }
    }

    /// <summary>
    /// This method takes our items source and generates visuals for
    /// each item in the collection; it can reuse visuals which were created
    /// previously and simply changes the binding context.
    /// </summary>
    /// <param name="newValue">New items to display</param>
    /// <exception cref="T:System.ArgumentNullException"></exception>
    void FillContainer(IList newValue)
    {
        var template = ItemTemplate;
        var visuals = Children;

        if (template == null)
            throw new NotSupportedException("ItemTemplate must be specified!");

        var newVisuals = new List<View>(Children); //using a list to avoid multiple layout refresh
        Children.Clear();

        for (int i = 0; i < newVisuals.Count; i++)
        {
            newVisuals[i].IsVisible = i < newValue.Count;
        }

        for (int i = 0; i < newValue.Count; i++)
        {
            var dataItem = newValue[i];

            if (visuals.Count > i)
            {
                if (template != null)
                {
                    var visualItem = visuals[i];
                    visualItem.BindingContext = dataItem;
                }
            }
            else
            {
                if (template != null)
                {
                    // Pull real template from selector if necessary.
                    var dSelector = template as DataTemplateSelector;
                    if (dSelector != null)
                        template = dSelector.SelectTemplate(dataItem, this);

                    var view = template.CreateContent() as View;
                    if (view != null)
                    {
                        view.BindingContext = dataItem;
                        newVisuals.Add(view);
                    }
                }
            }
        }

        foreach (var child in newVisuals) //wish they had a nice AddRange method here
            if(child.IsVisible)
                Children.Add(child);
    }

    /// <summary>
    /// This is called when the data source collection implements
    /// collection change notifications and the data has changed.
    /// This is not optimized - it simply replaces all the data.
    /// </summary>
    /// <param name="sender">Sender.</param>
    /// <param name="e">E.</param>
    void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        FillContainer((IList)sender);
    }
}

然后将您的 XAML 更改为:

<ListView ItemsSource="{Binding Departments}" HasUnevenRows="True">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <StackLayout Margin="20,20,20,20">
                    <Label Text="{Binding DepartmentName}" />
                    <Label Text="{Binding DepartmentId}" />

                    <local:ItemsControl ItemsSource="{Binding Employees}">
                        <local:ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <StackLayout Orientation="Horizontal">
                                    <Image Source="{Binding ImageUrl}"
                                                   WidthRequest="60"
                                                   HeightRequest="60"/>
                                    <Label Text="{Binding FirstName}"
                                           HorizontalTextAlignment="Center"
                                           VerticalTextAlignment="Center" />
                                </StackLayout>
                            </DataTemplate>
                        </local:ItemsControl.ItemTemplate>
                    </local:ItemsControl>
                </StackLayout>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>