Xamarin.Forms ListView 大小到内容?
Posted
技术标签:
【中文标题】Xamarin.Forms ListView 大小到内容?【英文标题】:Xamarin.Forms ListView size to content? 【发布时间】:2017-11-23 02:47:24 【问题描述】:我有一个相当大的表单(主要适用于平板电脑),其中有一个 TabbedPage
嵌套了一个 ScrollView
和一个包含许多控件的垂直 StackPanel
。
我很少有 ListView
包含一些单行项目,我需要它来调整内容的大小。
我想摆脱它的滚动条,但无论如何我不希望它占用超过其项目所需的空间。
有没有办法(甚至是丑陋的)无需编写渲染器 x3 平台即可实现这一目标?
这是一个伪描述我的树:
<ContentPage>
<MasterDetailPage>
<MasterDetailPage.Detail>
<TabbedPage>
<ContentPage>
<ScrollView>
<StackPanel>
<!-- many controls-->
<ListView>
渲染时,ListView
后面有一个巨大的差距。我怎样才能避免这种情况?
我尝试弄乱VerticalOptions
和HeightRequest
,但都不起作用。
我正在寻找一种不涉及自定义渲染器的动态方式(最好没有继承)。
【问题讨论】:
换句话说,您想将数据内容包装在 ListView 中并保留任何空间? 我想把ListView
设置为它的项目的必要高度,可以选择最大高度。
好的,把代码发给你
查看我的解决方案
也许可以跳过列表视图,直接将项目添加到您的堆栈中?
【参考方案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,此解决方案将起作用。类似问题:***.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,这个解决方案对我也不起作用。相反,它使列表视图滚动得如此之大,我什至无法看到页面中的其他剩余元素(标签和按钮)。以上是关于Xamarin.Forms ListView 大小到内容?的主要内容,如果未能解决你的问题,请参考以下文章
Android 上的 Xamarin.Forms ListView OutOfMemoryError 异常
Xamarin.Forms JSON 对象到 Listview
Xamarin.Forms:在地图上隐藏ListView点击?