【问题标题】:Xamarin.Forms ListView size to content?Xamarin.Forms ListView 大小到内容?
【发布时间】:2017-11-23 02:47:24
【问题描述】:

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

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

这是一个伪描述我的树:

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

渲染时,ListView 后面有一个巨大的差距。我怎样才能避免这种情况?

我尝试弄乱VerticalOptionsHeightRequest,但都不起作用。

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

【问题讨论】:

  • 换句话说,您想将数据内容包装在 ListView 中并保留任何空间?
  • 我想把 ListView 设置为它的项目的必要高度,可以选择最大高度。
  • 好的,把代码发给你
  • 查看我的解决方案
  • 也许可以跳过列表视图,直接将项目添加到您的堆栈中?

标签: listview xamarin.forms height sizetocontent


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    我正在使用 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;
        }
    }
    

    【讨论】:

      【解决方案3】:

      我可以制作一个考虑到 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 等来改变。 xml.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。

      【讨论】:

        【解决方案4】:

        好的,假设您的 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;
        

        希望对您有所帮助。

        【讨论】:

        • 如果您可以将 rowheight 设置为固定 90,此解决方案将起作用。类似问题:stackoverflow.com/questions/44501196/…
        • 有没有办法动态计算行高?不均匀行怎么办?
        • 如果为不均匀行设置,则必须计算单元格中的行高
        【解决方案5】:

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

        【讨论】:

        • ListView后面有东西时不起作用。至少在我写答案时没有。确保我在发布我的问题之前已经尝试过了。
        【解决方案6】:

        基于 Lutaaya 的 answer,我做了一个自动执行此操作的行为,确定和设置行高 (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 />
        

        【讨论】:

        • 嘿,很好的答案。它适用于减小列表视图的大小,但无法使其再次变大。
        • @Shimmy 上面的代码对我不起作用。 Cell.Renderheight 总是返回固定值,即 40 对于我来说,无论单元格内容如何。知道为什么会这样吗?
        • @user2185592 我不记得了,但你用什么填充单元格?您确定单元格内的布局大小正确吗?
        • 如果每个单元格的行高是动态的,这将不起作用。您有什么方法可以为动态单元格高度执行此操作吗?
        • @Shimmy,这个解决方案对我也不起作用。相反,它使列表视图滚动得如此之大,我什至无法看到页面中的其他剩余元素(标签和按钮)。
        猜你喜欢
        • 2020-02-08
        • 1970-01-01
        • 2021-03-19
        • 1970-01-01
        • 1970-01-01
        • 2018-07-19
        • 1970-01-01
        • 2020-03-08
        • 1970-01-01
        相关资源
        最近更新 更多