13

我有一个相当大的表单(主要适用于平板电脑),它有一个TabbedPage嵌套的 a和一个包含许多控件ScrollView的垂直。StackPanel

我很少有一个ListView包含一些单行项目的情况,我需要它来调整内容的大小。
我想摆脱它的滚动条,但无论如何我不希望它占用比其项目所需的空间更多的空间。
有没有办法(甚至是丑陋的)无需编写渲染器 x3 平台即可实现这一目标?

这是一个伪描述我的树:

<ContentPage>
  <MasterDetailPage>
    <MasterDetailPage.Detail>
      <TabbedPage>
        <ContentPage>
          <ScrollView>
            <StackPanel>
              <!-- many controls-->
              <ListView>

渲染时,在ListView. 我怎样才能避免这种情况?

我尝试弄乱VerticalOptionsand HeightRequest,但没有一个有效。

我正在寻找一种不涉及自定义渲染器的动态方式(最好没有继承)来实现这一目标。

4

6 回答 6

13

根据 Lutaaya 的回答,我做了一个自动执行此操作的行为,确定和设置行高(Gist)。

行为:

namespace Xamarin.Forms
{
  using System;
  using System.Linq;
  public class AutoSizeBehavior : Behavior<ListView>
  {
    ListView _ListView;
    ITemplatedItemsView<Cell> Cells => _ListView;

    protected override void OnAttachedTo(ListView bindable)
    {
      bindable.ItemAppearing += AppearanceChanged;
      bindable.ItemDisappearing += AppearanceChanged;
      _ListView = bindable;
    }

    protected override void OnDetachingFrom(ListView bindable)
    {
      bindable.ItemAppearing -= AppearanceChanged;
      bindable.ItemDisappearing -= AppearanceChanged;
      _ListView = null;
    }

    void AppearanceChanged(object sender, ItemVisibilityEventArgs e) =>
      UpdateHeight(e.Item);

    void UpdateHeight(object item)
    {
      if (_ListView.HasUnevenRows)
      {
        double height;
        if ((height = _ListView.HeightRequest) == 
            (double)VisualElement.HeightRequestProperty.DefaultValue)
          height = 0;

        height += MeasureRowHeight(item);
        SetHeight(height);
      }
      else if (_ListView.RowHeight == (int)ListView.RowHeightProperty.DefaultValue)
      {
        var height = MeasureRowHeight(item);
        _ListView.RowHeight = height;
        SetHeight(height);
      }
    }

    int MeasureRowHeight(object item)
    {
      var template = _ListView.ItemTemplate;
      var cell = (Cell)template.CreateContent();
      cell.BindingContext = item;
      var height = cell.RenderHeight;
      var mod = height % 1;
      if (mod > 0)
        height = height - mod + 1;
      return (int)height;
    }

    void SetHeight(double height)
    {
      //TODO if header or footer is string etc.
      if (_ListView.Header is VisualElement header)
        height += header.Height;
      if (_ListView.Footer is VisualElement footer)
        height += footer.Height;
      _ListView.HeightRequest = height;
    }
  }
}

用法:

<ContentPage xmlns:xf="clr-namespace:Xamarin.Forms">
  <ListView>
    <ListView.Behaviors>
      <xf:AutoSizeBehavior />
于 2017-07-05T03:57:35.813 回答
5

好的,假设您的 ListView 填充了 NewsFeeds,让我们使用一个ObservableCollection来包含我们的数据来填充 ListView,如下所示:

XAML 代码:

<ListView x:Name="newslist"/>

C# 代码

ObservableCollection <News> trends = new ObservableCollection<News>();

然后将趋势列表分配给 ListView :

newslist.ItemSource = trends;

然后,我们对 ListView 和数据做一些逻辑,这样 ListView 会包裹数据,随着数据的增加 ListView 也会增加,反之亦然

int i = trends.Count;
int heightRowList = 90;
i = (i * heightRowList);
newslist.HeightRequest = i;

因此完整的代码是:

ObservableCollection <News> trends = new ObservableCollection<News>();
newslist.ItemSource = trends;
int i = trends.Count;
int heightRowList = 90;
i = (i * heightRowList);
newslist.HeightRequest = i;

希望能帮助到你 。

于 2017-06-20T14:24:55.827 回答
4

我可以制作一个考虑到 ListView 单元格大小变化的事件处理程序。这是它:

    <ListView ItemsSource="{Binding Items}"
         VerticalOptions="Start"
         HasUnevenRows="true"
         CachingStrategy="RecycleElement"
         SelectionMode="None" 
         SizeChanged="ListView_OnSizeChanged">
               <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell >
                           <Frame Padding="10,0" SizeChanged="VisualElement_OnSizeChanged">

Frame 可以通过 Grid、StackLayout 等进行更改。xaml.cs:

        static readonly Dictionary<ListView, Dictionary<VisualElement, int>> _listViewHeightDictionary = new Dictionary<ListView, Dictionary<VisualElement, int>>();

    private void VisualElement_OnSizeChanged(object sender, EventArgs e)
    {
        var frame = (VisualElement) sender;
        var listView = (ListView)frame.Parent.Parent;
        var height = (int) frame.Measure(1000, 1000, MeasureFlags.IncludeMargins).Minimum.Height;
        if (!_listViewHeightDictionary.ContainsKey(listView))
        {
            _listViewHeightDictionary[listView] = new Dictionary<VisualElement, int>();
        }
        if (!_listViewHeightDictionary[listView].TryGetValue(frame, out var oldHeight) || oldHeight != height)
        {
            _listViewHeightDictionary[listView][frame] = height;
            var fullHeight = _listViewHeightDictionary[listView].Values.Sum();
            if ((int) listView.HeightRequest != fullHeight 
                && listView.ItemsSource.Cast<object>().Count() == _listViewHeightDictionary[listView].Count
                )
            {
                listView.HeightRequest = fullHeight;
                listView.Layout(new Rectangle(listView.X, listView.Y, listView.Width, fullHeight));
            }
        }
    }

    private void ListView_OnSizeChanged(object sender, EventArgs e)
    {
        var listView = (ListView)sender;
        if (listView.ItemsSource == null || listView.ItemsSource.Cast<object>().Count() == 0)
        {
            listView.HeightRequest = 0;
        }
    }

当 Frame 显示时(ListView.ItemTemplate 正在应用),frame 的大小发生变化。我们通过 Measure() 方法获取它的实际高度并将其放入 Dictionary 中,它知道当前 ListView 并保存 Frame 的高度。当显示最后一帧时,我们将所有高度相加。如果没有项目,ListView_OnSizeChanged() 将 listView.HeightRequest 设置为 0。

于 2019-03-29T07:04:06.673 回答
3

我可能在这里过分简化了事情,但只是添加HasUnevenRows="True"到 ListView 对我有用。

于 2018-07-21T07:00:01.637 回答
0

我知道这是一个旧线程,但到目前为止我找不到更好的解决方案 - 两年多后:-(

我正在使用 Xamarin.Forms v5.0.0.2244。实现并应用了 Shimmy 在上面的“用法:”中建议的行为,但是,AppearanceChanged 是针对每个列表视图项执行的,而不是列表视图本身。因此,当在 UpdateHeight 中测量高度时,它会测量单个项目的高度,并将列表视图的高度设置为该值 - 这是不正确的。这种行为似乎是合理的,因为它附加到 ItemAppearing 和 ItemDisappering 事件。

然而,按照 Shimmy 的想法,这里有一个改进(但远非完美)的实现。这个想法是处理项目的外观,测量这些项目并计算它们的总和,同时更新父列表视图的高度。

对我来说,初始高度计算不能正常工作,好像有些项目不会出现,因此不计算在内。我希望有人可以改进它,或者指出更好的解决方案:

internal class ListViewAutoShrinkBehavior : Behavior<ListView>
{
    ListView _listView;
    double _height = 0;

    protected override void OnAttachedTo(ListView listview)
    {
        listview.ItemAppearing += OnItemAppearing;
        listview.ItemDisappearing += OnItemDisappearing;
        _listView = listview;
    }

    protected override void OnDetachingFrom(ListView listview)
    {
        listview.ItemAppearing -= OnItemAppearing;
        listview.ItemDisappearing -= OnItemDisappearing;
        _listView = null;
    }

    void OnItemAppearing(object sender, ItemVisibilityEventArgs e)
    {
        _height += MeasureRowHeight(e.Item);
        SetHeight(_height);
    }

    void OnItemDisappearing(object sender, ItemVisibilityEventArgs e)
    {
        _height -= MeasureRowHeight(e.Item);
        SetHeight(_height);
    }

    int MeasureRowHeight(object item)
    {
        var template = _listView.ItemTemplate;
        var cell = (Cell)template.CreateContent();
        cell.BindingContext = item;
        double height = cell.RenderHeight;
        return (int)height;
    }

    void SetHeight(double height)
    {
        //TODO if header or footer is string etc.
        if (_listView.Header is VisualElement header)
            height += header.Height;
        if (_listView.Footer is VisualElement footer)
            height += footer.Height;
        _listView.HeightRequest = height;
    }
}
于 2021-12-27T23:29:44.957 回答
0

特别是当您的项目数量很少并且不需要滚动时,只需使用 bindableLayout。 https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/bindable-layouts 这总是完全按照大小呈现!如果您有复杂的项目,并且有交互,那么这些项目非常好。

于 2022-02-02T15:19:00.497 回答