Reputation: 4073
I'm developing a cross-platform app using Xamarin.Forms. In one of the pages I've an avatar image, some info, and then a ListView
. The thing is that I've all this wrapped in a ScrollView
, so I'm able to scroll all over the content. The issue with this is that I'm losing the ListView
scroll behaviour. This're some screenshots:
As you can see the XAML view is basically, the avatar Image
(the firetruck image), then the "Last check step" info, and then we've the ListView
. I hardcoded a HeightRequest
in the ListView
so It has a bigger height. But the thing is that when I scroll to the bottom of the page I can't keep scrolling through the ListView
because the ScrollView
messes with that behaviour. I need to use a ListView
because the list of check reports that I'm showing there is populated from a webservice, and I update that list when I enter and exit the page. This is the XAML code:
<?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>
So basically what I'm asking is how can achieve something like a parallax effect, scrolling down and then delegate the scroll behaviour to the ListView
that way I can scroll through every item in the ListView
. Currently I'm just able to see the items that fit in the ListView
HeightRequest dimension. In the example screenshot that I'm shoing for example, there are several other items in that ListView
but I can't scroll through them because the ScrollView
is messing with the ListView
scroll behaviour.
Upvotes: 0
Views: 2420
Reputation: 16199
The recommended approach for putting content on a page with a ListView is to use the Footer and Header properties of the ListView.
Have a look at ListView Headers and Footers
<ListView x:Name="listView">
<ListView.Footer>
<Label Text="Footer" />
</ListView.Footer>
</ListView>
This way you don't need to wrap in another scroll.
Upvotes: 3
Reputation: 1368
I believe that you need a RepeaterView
, a control that repeats items but is not scrolable and you can bind it to an ObservableCollection.
It just works
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;
}
Read this thread in Xamarin Forms Forums
Upvotes: 0
Reputation: 3228
ListView
should never be placed inside of a scrolling container. When you do this, you lose all benefits of the ListView (virtualization, scrolling, etc).
If you need to implement a parallax effect by scrolling some other content in sync with the ListView
position, then you can always overlay the list on top of another scrolling view (but not as a child of that scrolling view).
Upvotes: 1