Xamarin.Forms - ListView 在运行时改变高度
Xamarin.Forms - ListView change height at runtime
我正在使用 Xamarin.Forms 开发跨平台应用程序。在其中一个页面中,我有一个头像图像、一些信息,然后是 ListView
。问题是我将所有这些都包裹在 ScrollView
中,因此我可以滚动浏览所有内容。这个问题是我失去了 ListView
滚动行为。这是一些屏幕截图:
如您所见,XAML 视图基本上是头像 Image
(救火车图像),然后是 "Last check step" 信息,最后是 ListView
.我在 ListView
中硬编码了一个 HeightRequest
,所以它有更大的高度。但问题是,当我滚动到页面底部时,我无法继续滚动 ListView
,因为 ScrollView
会扰乱该行为。我 需要 使用 ListView
因为我在那里显示的检查报告列表是从网络服务填充的,我在进入和退出页面时更新该列表.这是 XAML 代码:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:checkReport="clr-namespace:HalliganTL.View;assembly=HalliganTL"
xmlns:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin.Abstractions"
x:Class="HalliganTL.View.ApparatusDetailPage" BackgroundColor="#FFFFFFFF">
<StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<ScrollView x:Name="GeneralScrollView"
Orientation="Vertical" VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="180"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="80"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="360"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Top Image -->
<ContentView Grid.Column="0" Grid.Row="0" Padding="15">
<controls:CircleImage x:Name="ApparatusImage"
HeightRequest="150" WidthRequest="150"
VerticalOptions="Center"
BorderColor="#3B5685"
BorderThickness="2"
HorizontalOptions="Center"
Aspect="AspectFill">
</controls:CircleImage>
</ContentView>
<!-- Last Check Separator -->
<AbsoluteLayout Grid.Column="0" Grid.Row="1" x:Name="LastCheckContainerTitle" BackgroundColor="#FFE4E4E9" Padding="5,15,15,5" VerticalOptions="End" >
<Label Text="LAST CHECK" Style="{StaticResource separatorLabel}" />
</AbsoluteLayout>
<!-- Last Check Detail -->
<checkReport:CheckReportViewPage Grid.Column="0" Grid.Row="2" x:Name="LastCheckReportView">
<checkReport:CheckReportViewPage.GestureRecognizers>
<TapGestureRecognizer Tapped="OnLastCheckReportClicked" NumberOfTapsRequired="1" />
</checkReport:CheckReportViewPage.GestureRecognizers>
</checkReport:CheckReportViewPage>
<AbsoluteLayout Grid.Column="0" Grid.Row="3" x:Name="CheckHistoryTitle" BackgroundColor="#FFE4E4E9" Padding="5,15,15,5" >
<Label Text="CHECK HISTORY" Style="{StaticResource separatorLabel}" FontAttributes="None" VerticalOptions="End" />
</AbsoluteLayout>
<!-- Apparatus check history -->
<ListView x:Name="CheckHistoryListView"
HeightRequest="380"
Grid.Column="0" Grid.Row="4"
HorizontalOptions="FillAndExpand"
HasUnevenRows="True"
IsPullToRefreshEnabled="False"
VerticalOptions="FillAndExpand" ItemTapped="OnItemTapped">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<checkReport:CheckReportViewPage/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ScrollView>
<Label Text="Start Check" HeightRequest="60" VerticalOptions="End" BackgroundColor="#3B5685" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" FontSize="18" TextColor="White">
<Label.GestureRecognizers>
<TapGestureRecognizer
Tapped="OnStartCheckClicked"
NumberOfTapsRequired="1" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</ContentPage>
所以基本上我要问的是如何实现类似视差效果的东西,向下滚动然后将滚动行为委托给 ListView
这样我就可以滚动 [=12] 中的每个项目=].目前我只能看到适合 ListView
HeightRequest 维度的项目。例如,在我正在播放的示例屏幕截图中,ListView
中还有其他几个项目,但我无法滚动它们,因为 ScrollView
扰乱了 ListView
滚动行为.
ListView
绝不能放在滚动容器中。当您这样做时,您将失去 ListView 的所有好处(虚拟化、滚动等)。
如果您需要通过滚动与 ListView
位置同步的其他内容来实现视差效果,那么您始终可以将列表覆盖在另一个滚动视图的顶部(但不能作为 child 那个滚动视图)。
我相信你需要一个 RepeaterView
,一个重复项目但不可滚动的控件,你可以将它绑定到 ObservableCollection。
很管用
using System;
using System.Collections;
using System.Collections.Specialized;
using Xamarin.Forms;
public delegate void RepeaterViewItemAddedEventHandler(object sender, RepeaterViewItemAddedEventArgs args);
// in lieu of an actual Xamarin Forms ItemsControl, this is a heavily modified version of code from https://forums.xamarin.com/discussion/21635/xforms-needs-an-itemscontrol
public class RepeaterView : StackLayout
{
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create<RepeaterView, IEnumerable>(
p => p.ItemsSource,
null,
BindingMode.OneWay,
propertyChanged: ItemsChanged);
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create<RepeaterView, DataTemplate>(
p => p.ItemTemplate,
default(DataTemplate));
public event RepeaterViewItemAddedEventHandler ItemCreated;
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
private static void ItemsChanged(BindableObject bindable, IEnumerable oldValue, IEnumerable newValue)
{
var control = (RepeaterView)bindable;
var oldObservableCollection = oldValue as INotifyCollectionChanged;
if (oldObservableCollection != null)
{
oldObservableCollection.CollectionChanged -= control.OnItemsSourceCollectionChanged;
}
var newObservableCollection = newValue as INotifyCollectionChanged;
if (newObservableCollection != null)
{
newObservableCollection.CollectionChanged += control.OnItemsSourceCollectionChanged;
}
control.Children.Clear();
if (newValue != null)
{
foreach (var item in newValue)
{
var view = control.CreateChildViewFor(item);
control.Children.Add(view);
control.OnItemCreated(view);
}
}
control.UpdateChildrenLayout();
control.InvalidateLayout();
}
protected virtual void OnItemCreated(View view) =>
this.ItemCreated?.Invoke(this, new RepeaterViewItemAddedEventArgs(view, view.BindingContext));
private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var invalidate = false;
if (e.OldItems != null)
{
this.Children.RemoveAt(e.OldStartingIndex);
invalidate = true;
}
if (e.NewItems != null)
{
for (var i = 0; i < e.NewItems.Count; ++i)
{
var item = e.NewItems[i];
var view = this.CreateChildViewFor(item);
this.Children.Insert(i + e.NewStartingIndex, view);
OnItemCreated(view);
}
invalidate = true;
}
if (invalidate)
{
this.UpdateChildrenLayout();
this.InvalidateLayout();
}
}
private View CreateChildViewFor(object item)
{
this.ItemTemplate.SetValue(BindableObject.BindingContextProperty, item);
return (View)this.ItemTemplate.CreateContent();
}
}
public class RepeaterViewItemAddedEventArgs : EventArgs
{
private readonly View view;
private readonly object model;
public RepeaterViewItemAddedEventArgs(View view, object model)
{
this.view = view;
this.model = model;
}
public View View => this.view;
public object Model => this.model;
}
在带有 ListView 的页面上放置内容的推荐方法是使用 ListView 的页脚和页眉属性。
看看ListView Headers and Footers
<ListView x:Name="listView">
<ListView.Footer>
<Label Text="Footer" />
</ListView.Footer>
</ListView>
这样你就不需要再卷起来了。
我正在使用 Xamarin.Forms 开发跨平台应用程序。在其中一个页面中,我有一个头像图像、一些信息,然后是 ListView
。问题是我将所有这些都包裹在 ScrollView
中,因此我可以滚动浏览所有内容。这个问题是我失去了 ListView
滚动行为。这是一些屏幕截图:
如您所见,XAML 视图基本上是头像 Image
(救火车图像),然后是 "Last check step" 信息,最后是 ListView
.我在 ListView
中硬编码了一个 HeightRequest
,所以它有更大的高度。但问题是,当我滚动到页面底部时,我无法继续滚动 ListView
,因为 ScrollView
会扰乱该行为。我 需要 使用 ListView
因为我在那里显示的检查报告列表是从网络服务填充的,我在进入和退出页面时更新该列表.这是 XAML 代码:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:checkReport="clr-namespace:HalliganTL.View;assembly=HalliganTL"
xmlns:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin.Abstractions"
x:Class="HalliganTL.View.ApparatusDetailPage" BackgroundColor="#FFFFFFFF">
<StackLayout Orientation="Vertical" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<ScrollView x:Name="GeneralScrollView"
Orientation="Vertical" VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="180"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="80"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="360"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Top Image -->
<ContentView Grid.Column="0" Grid.Row="0" Padding="15">
<controls:CircleImage x:Name="ApparatusImage"
HeightRequest="150" WidthRequest="150"
VerticalOptions="Center"
BorderColor="#3B5685"
BorderThickness="2"
HorizontalOptions="Center"
Aspect="AspectFill">
</controls:CircleImage>
</ContentView>
<!-- Last Check Separator -->
<AbsoluteLayout Grid.Column="0" Grid.Row="1" x:Name="LastCheckContainerTitle" BackgroundColor="#FFE4E4E9" Padding="5,15,15,5" VerticalOptions="End" >
<Label Text="LAST CHECK" Style="{StaticResource separatorLabel}" />
</AbsoluteLayout>
<!-- Last Check Detail -->
<checkReport:CheckReportViewPage Grid.Column="0" Grid.Row="2" x:Name="LastCheckReportView">
<checkReport:CheckReportViewPage.GestureRecognizers>
<TapGestureRecognizer Tapped="OnLastCheckReportClicked" NumberOfTapsRequired="1" />
</checkReport:CheckReportViewPage.GestureRecognizers>
</checkReport:CheckReportViewPage>
<AbsoluteLayout Grid.Column="0" Grid.Row="3" x:Name="CheckHistoryTitle" BackgroundColor="#FFE4E4E9" Padding="5,15,15,5" >
<Label Text="CHECK HISTORY" Style="{StaticResource separatorLabel}" FontAttributes="None" VerticalOptions="End" />
</AbsoluteLayout>
<!-- Apparatus check history -->
<ListView x:Name="CheckHistoryListView"
HeightRequest="380"
Grid.Column="0" Grid.Row="4"
HorizontalOptions="FillAndExpand"
HasUnevenRows="True"
IsPullToRefreshEnabled="False"
VerticalOptions="FillAndExpand" ItemTapped="OnItemTapped">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<checkReport:CheckReportViewPage/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ScrollView>
<Label Text="Start Check" HeightRequest="60" VerticalOptions="End" BackgroundColor="#3B5685" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" FontSize="18" TextColor="White">
<Label.GestureRecognizers>
<TapGestureRecognizer
Tapped="OnStartCheckClicked"
NumberOfTapsRequired="1" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</ContentPage>
所以基本上我要问的是如何实现类似视差效果的东西,向下滚动然后将滚动行为委托给 ListView
这样我就可以滚动 [=12] 中的每个项目=].目前我只能看到适合 ListView
HeightRequest 维度的项目。例如,在我正在播放的示例屏幕截图中,ListView
中还有其他几个项目,但我无法滚动它们,因为 ScrollView
扰乱了 ListView
滚动行为.
ListView
绝不能放在滚动容器中。当您这样做时,您将失去 ListView 的所有好处(虚拟化、滚动等)。
如果您需要通过滚动与 ListView
位置同步的其他内容来实现视差效果,那么您始终可以将列表覆盖在另一个滚动视图的顶部(但不能作为 child 那个滚动视图)。
我相信你需要一个 RepeaterView
,一个重复项目但不可滚动的控件,你可以将它绑定到 ObservableCollection。
很管用
using System;
using System.Collections;
using System.Collections.Specialized;
using Xamarin.Forms;
public delegate void RepeaterViewItemAddedEventHandler(object sender, RepeaterViewItemAddedEventArgs args);
// in lieu of an actual Xamarin Forms ItemsControl, this is a heavily modified version of code from https://forums.xamarin.com/discussion/21635/xforms-needs-an-itemscontrol
public class RepeaterView : StackLayout
{
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create<RepeaterView, IEnumerable>(
p => p.ItemsSource,
null,
BindingMode.OneWay,
propertyChanged: ItemsChanged);
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create<RepeaterView, DataTemplate>(
p => p.ItemTemplate,
default(DataTemplate));
public event RepeaterViewItemAddedEventHandler ItemCreated;
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
private static void ItemsChanged(BindableObject bindable, IEnumerable oldValue, IEnumerable newValue)
{
var control = (RepeaterView)bindable;
var oldObservableCollection = oldValue as INotifyCollectionChanged;
if (oldObservableCollection != null)
{
oldObservableCollection.CollectionChanged -= control.OnItemsSourceCollectionChanged;
}
var newObservableCollection = newValue as INotifyCollectionChanged;
if (newObservableCollection != null)
{
newObservableCollection.CollectionChanged += control.OnItemsSourceCollectionChanged;
}
control.Children.Clear();
if (newValue != null)
{
foreach (var item in newValue)
{
var view = control.CreateChildViewFor(item);
control.Children.Add(view);
control.OnItemCreated(view);
}
}
control.UpdateChildrenLayout();
control.InvalidateLayout();
}
protected virtual void OnItemCreated(View view) =>
this.ItemCreated?.Invoke(this, new RepeaterViewItemAddedEventArgs(view, view.BindingContext));
private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var invalidate = false;
if (e.OldItems != null)
{
this.Children.RemoveAt(e.OldStartingIndex);
invalidate = true;
}
if (e.NewItems != null)
{
for (var i = 0; i < e.NewItems.Count; ++i)
{
var item = e.NewItems[i];
var view = this.CreateChildViewFor(item);
this.Children.Insert(i + e.NewStartingIndex, view);
OnItemCreated(view);
}
invalidate = true;
}
if (invalidate)
{
this.UpdateChildrenLayout();
this.InvalidateLayout();
}
}
private View CreateChildViewFor(object item)
{
this.ItemTemplate.SetValue(BindableObject.BindingContextProperty, item);
return (View)this.ItemTemplate.CreateContent();
}
}
public class RepeaterViewItemAddedEventArgs : EventArgs
{
private readonly View view;
private readonly object model;
public RepeaterViewItemAddedEventArgs(View view, object model)
{
this.view = view;
this.model = model;
}
public View View => this.view;
public object Model => this.model;
}
在带有 ListView 的页面上放置内容的推荐方法是使用 ListView 的页脚和页眉属性。
看看ListView Headers and Footers
<ListView x:Name="listView">
<ListView.Footer>
<Label Text="Footer" />
</ListView.Footer>
</ListView>
这样你就不需要再卷起来了。