【问题标题】:How can I display items in a ListView horizontally in Xamarin.forms xaml?如何在 Xamarin.forms xaml 中水平显示 ListView 中的项目?
【发布时间】:2018-05-28 23:08:52
【问题描述】:

我在ListView 中显示 SQLite 对象列表,但我希望它们水平显示。所以不要这样:

| longitem        |
| item            |
| evenlongeritem  |
| item            |
| longeritem      |

我想要这个:

| longitem item   |
| evenlongeritem  |
| item longeritem |

重要的是,项目可以有不同的宽度,因此仅将列表分成一定数量的列会有所改进,但并不理想。我也不知道物品的数量。

这是我目前拥有的代码:

<ListView x:Name="inactiveList" VerticalOptions="Start" ItemTapped="PutBack">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextCell Text="{Binding Name}" TextColor="Black">
                  <TextCell.ContextActions>
                         <MenuItem Command="{Binding Source={x:Reference ListPage}, Path=DeleteListItem}" CommandParameter="{Binding .}" Text="delete" />
                  </TextCell.ContextActions>
              </TextCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

代码背后:

public ListPage()
{
    InitializeComponent();

    ObservableCollection<ListItem> activeItems =
        new ObservableCollection<ListItem>(
            App.ListItemRepo.GetActiveListItems());
    activeList.ItemsSource = activeItems;
    ...

我尝试将ViewCell 包裹在水平的StackLayout 中,但出现此错误:

未处理的异常:System.InvalidCastException:指定的转换是 无效。

我不确定该错误是否意味着,但我认为不可能在 DataTemplate 中添加 StackLayout。我也无法使ListView 水平。

--

更新 4:

我终于可以将简单的标签水平列出,但现在我无法重新创建垂直 ListView 中内置的点击和长按操作。有可能吗?

ListView.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns:local="clr-namespace:Myapp">
        <!-- ... -->
        <local:WrapLayout x:Name="inactiveList" ItemsSource="{Binding .}" Spacing="5" />

ListView.xaml.cs

using Myapp.Models;
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using SQLite;
using System.Threading.Tasks;
using System.IO;
using Xamarin.Forms;
using System.Diagnostics;
using DLToolkit.Forms.Controls;

namespace Myapp
{
    public partial class ListPage
    {
        ...
        public ListPage()
        {
            InitializeComponent();

            ObservableCollection<ListItem> inactiveItems =
                new ObservableCollection<ListItem>(
                    App.ListItemRepo.GetInactiveListItems());
            inactiveList.ItemsSource = inactiveItems;
            inactiveList.HeightRequest = 50 * inactiveItems.Count;


        }
        ...
    }

    public class WrapLayout : Layout<View>
    {

        public ObservableCollection<ListItem> ItemsSource
        {
            get { return (ObservableCollection<ListItem>)GetValue(ItemSourceProperty); }
            set { SetValue(ItemSourceProperty, value); }
        }

        public static readonly BindableProperty ItemSourceProperty =
            BindableProperty.Create
            (
                "ItemsSource",
                typeof(ObservableCollection<ListItem>),
                typeof(WrapLayout),
                propertyChanged: (bindable, oldvalue, newvalue) => ((WrapLayout)bindable).AddViews()
            );


        void AddViews()
        {
            Children.Clear();
            foreach (ListItem s in ItemsSource)
            {
                Button button = new Button();
                button.BackgroundColor = Color.Red;
                button.Text = s.Name;
                button.TextColor = Color.Black;
                button.Clicked = "{Binding Source={x:Reference ListPage}, Path=PutBack}";
                Children.Add(button);
            }
        }

        public static readonly BindableProperty SpacingProperty =
            BindableProperty.Create
            (
                "Spacing",
                typeof(double),
                typeof(WrapLayout),
                10.0,
                propertyChanged: (bindable, oldvalue, newvalue) => ((WrapLayout)bindable).OnSizeChanged()
            );

        public double Spacing
        {
            get { return (double)GetValue(SpacingProperty); }
            set { SetValue(SpacingProperty, value); }
        }

        private void OnSizeChanged()
        {
            this.ForceLayout();
        }

        protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
        {
            if (WidthRequest > 0)
                widthConstraint = Math.Min(widthConstraint, WidthRequest);
            if (HeightRequest > 0)
                heightConstraint = Math.Min(heightConstraint, HeightRequest);

            double internalWidth = double.IsPositiveInfinity(widthConstraint) ? double.PositiveInfinity : Math.Max(0, widthConstraint);
            double internalHeight = double.IsPositiveInfinity(heightConstraint) ? double.PositiveInfinity : Math.Max(0, heightConstraint);

            return DoHorizontalMeasure(internalWidth, internalHeight);
        }

        private SizeRequest DoHorizontalMeasure(double widthConstraint, double heightConstraint)
        {
            int rowCount = 1;

            double width = 0;
            double height = 0;
            double minWidth = 0;
            double minHeight = 0;
            double widthUsed = 0;

            foreach (var item in Children)
            {
                var size = item.Measure(widthConstraint, heightConstraint);

                height = Math.Max(height, size.Request.Height);

                var newWidth = width + size.Request.Width + Spacing;
                if (newWidth > widthConstraint)
                {
                    rowCount++;
                    widthUsed = Math.Max(width, widthUsed);
                    width = size.Request.Width;
                }
                else
                    width = newWidth;

                minHeight = Math.Max(minHeight, size.Minimum.Height);
                minWidth = Math.Max(minWidth, size.Minimum.Width);
            }

            if (rowCount > 1)
            {
                width = Math.Max(width, widthUsed);
                height = (height + Spacing) * rowCount - Spacing; // via MitchMilam 
            }

            return new SizeRequest(new Size(width, height), new Size(minWidth, minHeight));
        }

        protected override void LayoutChildren(double x, double y, double width, double height)
        {
            double rowHeight = 0;
            double yPos = y, xPos = x;

            foreach (var child in Children.Where(c => c.IsVisible))
            {
                var request = child.Measure(width, height);

                double childWidth = request.Request.Width;
                double childHeight = request.Request.Height;
                rowHeight = Math.Max(rowHeight, childHeight);

                if (xPos + childWidth > width)
                {
                    xPos = x;
                    yPos += rowHeight + Spacing;
                    rowHeight = 0;
                }

                var region = new Rectangle(xPos, yPos, childWidth, childHeight);
                LayoutChildIntoBoundingRegion(child, region);
                xPos += region.Width + Spacing;
            }
        }
    }
}

【问题讨论】:

  • 在 ItemsPanelTemplate 中使用 UniformGrid,列设置为您需要的数量,行 = 1
  • @KevinCook 问题是,我不确定我需要多少列,因为不同的项目会有不同的长度。
  • 1) 使用自定义布局,Xamarin 有一个符合您需求的示例,WrapLayout:developer.xamarin.com/guides/xamarin-forms/user-interface/…(注意:我不知道您希望在此配置中有多少项目,所以性能/内存( !)可能是一个问题)。 2) 否则 CollectionViews...
  • @SushiHangover 我不确定我是否正确阅读指南。我是否必须在后面的代码中添加一个新类才能将 WrapLayout 添加到我的 xaml 中?
  • 是的,您正在项目中创建一个名为 WrapLayoutLayout&lt;View&gt; 的新子类,然后您将在 XAML(或代码)中使用它

标签: c# xaml listview xamarin xamarin.forms


【解决方案1】:

请参阅My Post。和你的情况类似。

只需要自定义Layout 并管理它的大小和它的孩子的排列。

更新:

我收到“Type local:WrapLayout not found in xmlns clr-namespace:Myapp”错误。

WrapLayout 设为 public 类,将其与 ListPage 分开。

我也有点困惑如何在这里应用数据绑定

我们需要在wraplayout里面添加一个名为ItemSourceBindableProperty,并在属性改变时添加子视图。


Xmal

 <ContentPage.Content>
    <local:WrapLayout x:Name="wrap" ItemSource="{Binding .}" Spacing="5" />
</ContentPage.Content>

背后的代码

List<string> list = new List<string> {
            "11111111111111111111111",
            "22222",
            "333333333333333",
            "4",
            "55555555",
            "6666666666666666666666",
            "77777",
            "8888888888",
            "99999999999999999999999999999999"
};
this.BindingContext = list;

更新:按钮事件

我们可以在WrapLayout中定义event,当我们点击或长按按钮时,触发事件。关于长按,我们应该创建自定义渲染器来实现它。

包装布局

namespace ImageWrapLayout
{
public class ButtonWithLongPressGesture : Button
{
    public EventHandler LongPressHandle;
    public EventHandler TapHandle;

    public void HandleLongPress(object sender, EventArgs e)
    {
        //Handle LongPressActivated Event
        LongPressHandle(sender, e);
    }

    public void HandleTap(object sender, EventArgs e)
    {
        //Handle Tap Event
        TapHandle(sender, e);
    }
}


public class WrapLayout : Layout<View>
{

    public List<string> ItemSource
    {
        get { return (List<string>)GetValue(ItemSourceProperty); }
        set { SetValue(ItemSourceProperty, value); }
    }

    public static readonly BindableProperty ItemSourceProperty =
        BindableProperty.Create
        (
            "ItemSource",
            typeof(List<string>),
            typeof(WrapLayout),
            propertyChanged: (bindable, oldvalue, newvalue) => ((WrapLayout)bindable).AddViews()
        );


    void AddViews()
    {
        Children.Clear();
        foreach (string s in ItemSource)
        {
            ButtonWithLongPressGesture button = new ButtonWithLongPressGesture();
            button.BackgroundColor = Color.Red;
            button.Text = s;
            button.TextColor = Color.Black;
            Children.Add(button);

            button.TapHandle += WrapLayoutTapHandle;
            button.LongPressHandle = WrapLayoutLongPressHandle;
        }
    }


    public EventHandler WrapLayoutLongPressHandle;
    public EventHandler WrapLayoutTapHandle;




    public static readonly BindableProperty SpacingProperty =
        BindableProperty.Create
        (
            "Spacing",
            typeof(double),
            typeof(WrapLayout),
            10.0,
            propertyChanged: (bindable, oldvalue, newvalue) => ((WrapLayout)bindable).OnSizeChanged()
        );

    public double Spacing
    {
        get { return (double)GetValue(SpacingProperty); }
        set { SetValue(SpacingProperty, value); }
    }

    private void OnSizeChanged()
    {
        this.ForceLayout();
    }

    protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
    {
        if (WidthRequest > 0)
            widthConstraint = Math.Min(widthConstraint, WidthRequest);
        if (HeightRequest > 0)
            heightConstraint = Math.Min(heightConstraint, HeightRequest);

        double internalWidth = double.IsPositiveInfinity(widthConstraint) ? double.PositiveInfinity : Math.Max(0, widthConstraint);
        double internalHeight = double.IsPositiveInfinity(heightConstraint) ? double.PositiveInfinity : Math.Max(0, heightConstraint);

        return DoHorizontalMeasure(internalWidth, internalHeight);
    }

    private SizeRequest DoHorizontalMeasure(double widthConstraint, double heightConstraint)
    {
        int rowCount = 1;

        double width = 0;
        double height = 0;
        double minWidth = 0;
        double minHeight = 0;
        double widthUsed = 0;

        foreach (var item in Children)
        {
            var size = item.Measure(widthConstraint, heightConstraint);

            height = Math.Max(height, size.Request.Height);

            var newWidth = width + size.Request.Width + Spacing;
            if (newWidth > widthConstraint)
            {
                rowCount++;
                widthUsed = Math.Max(width, widthUsed);
                width = size.Request.Width;
            }
            else
                width = newWidth;

            minHeight = Math.Max(minHeight, size.Minimum.Height);
            minWidth = Math.Max(minWidth, size.Minimum.Width);
        }

        if (rowCount > 1)
        {
            width = Math.Max(width, widthUsed);
            height = (height + Spacing) * rowCount - Spacing; // via MitchMilam 
        }

        return new SizeRequest(new Size(width, height), new Size(minWidth, minHeight));
    }

    protected override void LayoutChildren(double x, double y, double width, double height)
    {
        double rowHeight = 0;
        double yPos = y, xPos = x;

        foreach (var child in Children.Where(c => c.IsVisible))
        {
            var request = child.Measure(width, height);

            double childWidth = request.Request.Width;
            double childHeight = request.Request.Height;
            rowHeight = Math.Max(rowHeight, childHeight);

            if (xPos + childWidth > width)
            {
                xPos = x;
                yPos += rowHeight + Spacing;
                rowHeight = 0;
            }

            var region = new Rectangle(xPos, yPos, childWidth, childHeight);
            LayoutChildIntoBoundingRegion(child, region);
            xPos += region.Width + Spacing;
        }
    }
}
}

LongPress 的自定义渲染器

[assembly: ExportRenderer(typeof(ButtonWithLongPressGesture), typeof(LongPressGestureRecognizerButtonRenderer))]
namespace ImageWrapLayout.iOS
{
class LongPressGestureRecognizerButtonRenderer : ButtonRenderer
{
    ButtonWithLongPressGesture view;

    public LongPressGestureRecognizerButtonRenderer()
    {
        this.AddGestureRecognizer(new UILongPressGestureRecognizer((longPress) => {
            if (longPress.State == UIGestureRecognizerState.Began)
            {
                view.HandleLongPress(view, new EventArgs());
            }
        }));
    }

    protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
    {
        base.OnElementChanged(e);

        if (e.NewElement != null)
            view = e.NewElement as ButtonWithLongPressGesture;

        //if(Control == null)
        //{
        UIButton but = Control as UIButton;
            but.TouchUpInside += (sender, e1) => {
                view.HandleTap(view, new EventArgs());
            };
        //}
    }
}
}

用法(在 Page.cs 中)

 inactiveList.WrapLayoutLongPressHandle += (sender, e) =>
 {
 };

 inactiveList.WrapLayoutTapHandle += (sender, e) =>
 {
 };

【讨论】:

  • 我尝试了您的代码,但出现“Type local:WrapLayout not found in xmlns clr-namespace:Myapp”错误。我也有点困惑如何在这里应用数据绑定。我可以把DataTemplate 放在WrapLayout 里面然后给WrapLayout 一个ItemsSource 吗?
  • @JoeMorano 再次检查我的更新,了解错误的原因。
  • 我修复了该错误,但现在当我尝试添加数据绑定时,我收到此错误:“异常:System.ArrayTypeMismatchException:尝试将元素作为与数组不兼容的类型访问。发生”我更新了我的代码。你知道错误是什么意思吗?
  • @JoeMorano 我看到你在 wraplayout 中设置了 itemsource。并且还绑定了 xmal 中的数据。你只需要在 xmal 中设置。参考我的示例代码
  • 我认为使用您的代码,wraplayout 不会获得任何数据。我想通过调用new ObservableCollection&lt;ListItem&gt;(App.ListItemRepo.GetInactiveListItems()); 来显示 ListItems 的 ObservableCollection。我应该在哪里调用GetInactiveListItems 方法?
【解决方案2】:

你可以使用这个:https://github.com/daniel-luberda/DLToolkit.Forms.Controls/tree/master/FlowListView

它与 ListView 使用相同,但支持列

【讨论】:

  • 这是否支持不均匀的列?
  • 我尝试实现这个,但列表根本没有显示出来。
  • @JoeMorano 我也有这个问题。 FlowListView 还定义了几个名称类似于原生 ListView 除了已经存在的属性的属性,这也无济于事。谈论混乱!
【解决方案3】:

技术上你可以做到。所有的 VisualElements 都有一个 Rotation BindableProperty,所​​以将旋转设置为 270。

public static readonly BindableProperty RotationProperty;
public static readonly BindableProperty RotationXProperty;
public static readonly BindableProperty RotationYProperty;

此代码来自可视元素类。另请参阅下面的示例。

<ListView x:Name="MessagesListView" Rotation="270" ItemsSource="{Binding Items}" RowHeight="40">
  <ListView.ItemTemplate>
    <DataTemplate>
      <ViewCell>
        <ViewCell.View>
          <StackLayout>
            <!--mylayouthere-->
          </StackLayout>
        </ViewCell.View>
      </ViewCell>
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-31
    相关资源
    最近更新 更多