Как отобразить элементы в ListView горизонтально в Xamarin.формы в XAML?

я показываю список объектов SQLite в ListView, но я хочу, чтобы отобразить по горизонтали. Поэтому вместо этого:

| 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, но я получил эту ошибку:

Необработанное Исключение: Система.InvalidCastException: указанное приведение недействительный.

я не уверен, что эта ошибка означает, но я не думаю, что можно добавить StackLayout внутри DataTemplate. Я также не могу сделать 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;
            }
        }
    }
}

3 ответов


смотрите Мой Пост. Это похоже на ваш случай.

просто нужно custom Layout и управляйте своим размером и расположением своих детей.

обновление:

я получаю ошибку" тип local:WrapLayout не найден в xmlns clr-namespace:Myapp".

Создать Класс WrapLayout государственные , отделить его от ListPage.

я также немного смущен тем, как применять привязку данных вот!--15-->

нам нужно добавить BindableProperty имени ItemSource внутри wraplayout и добавить подвида при изменении свойства.


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;

Обновление: Событие Кнопки

мы можем определить event внутри WrapLayout, когда мы нажимаем или долго нажимаем на кнопку, запускаем события. И о долгой печати мы должны создать пользовательские рендеры для реализовать его.

WrapLayout

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());
            };
        //}
    }
}
}

Использование (На Стр.cs)

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

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

вы можете использовать это: https://github.com/daniel-luberda/DLToolkit.Forms.Controls/tree/master/FlowListView

Он используется так же, как ListView, но имеет поддержку столбце

enter image description here


технически вы можете это сделать. Все VisualElements имеют вращение BindableProperty, поэтому установите вращение на 270.

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

этот код из класса Visual Element. Также см. Образец ниже.

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