我正在使用Xamarin.Forms开发跨平台应用。在其中一个页面中,我有一个头像图片,一些信息,然后是ListView
。问题是我将所有这些包裹在ScrollView
中,因此我可以滚动整个内容。这个问题是我失去了ListView
滚动行为。这是一些截图:
正如您所看到的,XAML视图基本上是化身Image
(救火车图像),然后是"最后检查步骤"信息,然后我们ListView
。我在HeightRequest
中对ListView
进行了硬编码,因此它的高度更高。但问题是,当我滚动到页面底部时,我无法滚动浏览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
,这样我就可以滚动浏览{{1}中的每个项目}}。目前,我只能看到适合ListView
HeightRequest维度的项目。在我举例说明的示例屏幕截图中,ListView
中还有其他几个项目,但我无法滚动浏览它们,因为ListView
正在弄乱{{1}滚动行为。
答案 0 :(得分:3)
使用ListView将内容放在页面上的推荐方法是使用ListView的页脚和页眉属性。
查看ListView Headers and Footers
<ListView x:Name="listView">
<ListView.Footer>
<Label Text="Footer" />
</ListView.Footer>
</ListView>
这样您就不需要换行另一个滚动。
答案 1 :(得分:1)
ListView
放在滚动容器内。当您这样做时,您将失去ListView的所有好处(虚拟化,滚动等)。
如果您需要通过滚动其他内容与ListView
位置同步来实现视差效果,那么您始终可以将列表覆盖在另一个滚动视图的顶部(但不能作为子项<滚动视图的/ em>。
答案 2 :(得分:0)
我相信你需要一个RepeaterView
,一个重复项但不能被scrolable的控件,你可以将它绑定到一个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;
}