[Android/iOS] RemainingItemsThreshold and Scrolled implementation for CollectionView (#5754)

* infinite scroll capability

* remove added line

* scroll event implementation

* add offset calculation for GridLayoutManager

* removed custom layout managers

* renamed variables

* changed comment

* implement CenterItemIndex

* fixed pageSize

* removed artifact

* handle the case when the layout manager is not linear

* fix comment

* call base dispose

* code review changes

* remove unused references

* indentation fix

* fix compilation issues

* revert cleanup

* fix

* fix crash

* fix test

* fix index issue

* removed unused method

* fix

* moved private variables into app scope

* Added back UI test

* Name to AutomationID

* added command parameter and addressed minor suggestions
This commit is contained in:
adrianknight89 2019-07-25 08:07:28 -05:00 коммит произвёл Gerald Versluis
Родитель 248e1ce456
Коммит 03afb32d2b
14 изменённых файлов: 525 добавлений и 22 удалений

Просмотреть файл

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8" ?>
<controls:TestContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Xamarin.Forms.Controls"
x:Class="Xamarin.Forms.Controls.Issues.Github5623">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackLayout Orientation="Vertical" Spacing="5" Grid.Row="0" VerticalOptions="Center">
<Label x:Name="Label" LineBreakMode="WordWrap" Text="Scroll down until you hit 99" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
<Label x:Name="Label1" LineBreakMode="WordWrap" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
<Label x:Name="Label2" LineBreakMode="WordWrap" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
<Label x:Name="Label3" LineBreakMode="WordWrap" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
<Label x:Name="Label4" LineBreakMode="WordWrap" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
<Label x:Name="Label5" LineBreakMode="WordWrap" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
<Label x:Name="Label6" LineBreakMode="WordWrap" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
<Label x:Name="Label7" LineBreakMode="WordWrap" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
</StackLayout>
<CollectionView Grid.Row="1" AutomationId="CollectionView5623" ItemSizingStrategy="{Binding ItemSizingStrategy}" ItemsSource="{Binding Items}" Scrolled="CollectionView_OnScrolled" RemainingItemsThreshold="25" RemainingItemsThresholdReached="CollectionView_RemainingItemsThresholdReached" RemainingItemsThresholdReachedCommand="{Binding RemainingItemsThresholdReachedCommand}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid HeightRequest="{Binding Height}" BackgroundColor="{Binding BackgroundColor}">
<StackLayout Spacing="10" HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="{Binding Text}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>
<Label Text="{Binding HeightText}" FontSize="Micro" HorizontalTextAlignment="Center" VerticalTextAlignment="End"/>
</StackLayout>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<BoxView Grid.Row="1" HorizontalOptions="FillAndExpand" VerticalOptions="Center" BackgroundColor="Red" HeightRequest="5"/>
</Grid>
</controls:TestContentPage>

Просмотреть файл

@ -0,0 +1,196 @@
using System.Collections.ObjectModel;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
using System;
using System.Security.Cryptography;
using Xamarin.Forms.Xaml;
using System.Threading;
using System.Collections.Generic;
using System.Threading.Tasks;
#if UITEST
using Xamarin.UITest;
using Xamarin.UITest.Queries;
using NUnit.Framework;
using Xamarin.Forms.Core.UITests;
using System.Linq;
#endif
namespace Xamarin.Forms.Controls.Issues
{
#if UITEST
[Category(UITestCategories.CollectionView)]
#endif
#if APP
[XamlCompilation(XamlCompilationOptions.Compile)]
#endif
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 5623, "CollectionView with Incremental Collection (RemainingItemsThreshold)", PlatformAffected.All)]
public partial class Github5623 : TestContentPage
{
#if APP
int _itemCount = 10;
const int MaximumItemCount = 100;
const int PageSize = 10;
static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
public Github5623()
{
Device.SetFlags(new List<string> { CollectionView.CollectionViewExperimental });
InitializeComponent();
BindingContext = new ViewModel5623();
}
async void CollectionView_RemainingItemsThresholdReached(object sender, System.EventArgs e)
{
await SemaphoreSlim.WaitAsync();
try
{
var itemsSource = (sender as CollectionView).ItemsSource as ObservableCollection<Model5623>;
var nextSet = await GetNextSetAsync();
// nothing to add
if (nextSet.Count == 0)
return;
Device.BeginInvokeOnMainThread(() =>
{
foreach (var item in nextSet)
{
itemsSource.Add(item);
}
});
System.Diagnostics.Debug.WriteLine("Count: " + itemsSource.Count);
}
finally
{
SemaphoreSlim.Release();
}
}
void CollectionView_OnScrolled(object sender, ItemsViewScrolledEventArgs e)
{
Label1.Text = "HorizontalDelta: " + e.HorizontalDelta;
Label2.Text = "VerticalDelta: " + e.VerticalDelta;
Label3.Text = "HorizontalOffset: " + e.HorizontalOffset;
Label4.Text = "VerticalOffset: " + e.VerticalOffset;
Label5.Text = "FirstVisibleItemIndex: " + e.FirstVisibleItemIndex;
Label6.Text = "CenterItemIndex: " + e.CenterItemIndex;
Label7.Text = "LastVisibleItemIndex: " + e.LastVisibleItemIndex;
}
async Task<ObservableCollection<Model5623>> GetNextSetAsync()
{
return await Task.Run(() =>
{
var collection = new ObservableCollection<Model5623>();
var count = PageSize;
if (_itemCount + count > MaximumItemCount)
count = MaximumItemCount - _itemCount;
for (var i = _itemCount; i < _itemCount + count; i++)
{
collection.Add(new Model5623((BindingContext as ViewModel5623).ItemSizingStrategy == ItemSizingStrategy.MeasureAllItems)
{
Text = i.ToString(),
BackgroundColor = i % 2 == 0 ? Color.AntiqueWhite : Color.Lavender
});
}
_itemCount += count;
return collection;
});
}
#endif
protected override void Init()
{
}
#if UITEST
[Test]
public void CollectionViewInfiniteScroll()
{
RunningApp.WaitForElement ("CollectionView5623");
var colView = RunningApp.Query("CollectionView5623").Single();
AppResult[] lastCellResults = null;
RunningApp.RetryUntilPresent(() =>
{
RunningApp.DragCoordinates(colView.Rect.CenterX, colView.Rect.Y + colView.Rect.Height - 50, colView.Rect.CenterX, colView.Rect.Y + 5);
lastCellResults = RunningApp.Query("99");
return lastCellResults;
}, 100, 1);
Assert.IsTrue(lastCellResults?.Any() ?? false);
}
#endif
}
[Preserve(AllMembers = true)]
public class ViewModel5623
{
public ObservableCollection<Model5623> Items { get; set; }
public Command RemainingItemsThresholdReachedCommand { get; set; }
public ItemSizingStrategy ItemSizingStrategy { get; set; } = ItemSizingStrategy.MeasureAllItems;
public ViewModel5623()
{
var collection = new ObservableCollection<Model5623>();
var pageSize = 10;
for (var i = 0; i < pageSize; i++)
{
collection.Add(new Model5623(ItemSizingStrategy == ItemSizingStrategy.MeasureAllItems)
{
Text = i.ToString(),
BackgroundColor = i % 2 == 0 ? Color.AntiqueWhite : Color.Lavender
});
}
Items = collection;
RemainingItemsThresholdReachedCommand = new Command(() =>
{
System.Diagnostics.Debug.WriteLine($"{nameof(RemainingItemsThresholdReachedCommand)} called");
});
}
}
[Preserve(AllMembers = true)]
public class Model5623
{
RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
public string Text { get; set; }
public Color BackgroundColor { get; set; }
public int Height { get; set; } = 200;
public string HeightText { get; private set; }
public Model5623(bool isUneven)
{
var byteArray = new byte[4];
provider.GetBytes(byteArray);
if (isUneven)
Height = 100 + (BitConverter.ToInt32(byteArray, 0) % 300 + 300) % 300;
HeightText = "(Height: " + Height + ")";
}
}
}

Просмотреть файл

@ -25,6 +25,10 @@
<Compile Include="$(MSBuildThisFileDirectory)Issue6258.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue3150.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue6262.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Github5623.xaml.cs">
<DependentUpon>Github5623.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla59172.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FlagTestHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue6260.cs" />
@ -480,7 +484,6 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Issue4600.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue5252.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue5057.xaml.cs">
<DependentUpon>Issue5057.xaml</DependentUpon>
<SubType>Code</SubType>
@ -1304,4 +1307,10 @@
<Generator>MSBuild:Compile</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Github5623.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</EmbeddedResource>
</ItemGroup>
</Project>

Просмотреть файл

@ -2,8 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Windows.Input;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms
@ -44,6 +43,23 @@ namespace Xamarin.Forms
set => SetValue(ItemsSourceProperty, value);
}
public static readonly BindableProperty RemainingItemsThresholdReachedCommandProperty =
BindableProperty.Create(nameof(RemainingItemsThresholdReachedCommand), typeof(ICommand), typeof(ItemsView), null);
public ICommand RemainingItemsThresholdReachedCommand
{
get => (ICommand)GetValue(RemainingItemsThresholdReachedCommandProperty);
set => SetValue(RemainingItemsThresholdReachedCommandProperty, value);
}
public static readonly BindableProperty RemainingItemsThresholdReachedCommandParameterProperty = BindableProperty.Create(nameof(RemainingItemsThresholdReachedCommandParameter), typeof(object), typeof(ItemsView), default(object));
public object RemainingItemsThresholdReachedCommandParameter
{
get => GetValue(RemainingItemsThresholdReachedCommandParameterProperty);
set => SetValue(RemainingItemsThresholdReachedCommandParameterProperty, value);
}
public static readonly BindableProperty HorizontalScrollBarVisibilityProperty = BindableProperty.Create(
nameof(HorizontalScrollBarVisibility),
typeof(ScrollBarVisibility),
@ -69,6 +85,15 @@ namespace Xamarin.Forms
set => SetValue(VerticalScrollBarVisibilityProperty, value);
}
public static readonly BindableProperty RemainingItemsThresholdProperty =
BindableProperty.Create(nameof(RemainingItemsThreshold), typeof(int), typeof(ItemsView), -1, validateValue: (bindable, value) => (int)value >= -1);
public int RemainingItemsThreshold
{
get => (int)GetValue(RemainingItemsThresholdProperty);
set => SetValue(RemainingItemsThresholdProperty, value);
}
public void AddLogicalChild(Element element)
{
_logicalChildren.Add(element);
@ -142,8 +167,29 @@ namespace Xamarin.Forms
OnScrollToRequested(new ScrollToRequestEventArgs(item, group, position, animate));
}
public void SendRemainingItemsThresholdReached()
{
RemainingItemsThresholdReached?.Invoke(this, EventArgs.Empty);
if (RemainingItemsThresholdReachedCommand?.CanExecute(RemainingItemsThresholdReachedCommandParameter) == true)
RemainingItemsThresholdReachedCommand?.Execute(RemainingItemsThresholdReachedCommandParameter);
OnRemainingItemsThresholdReached();
}
public void SendScrolled(ItemsViewScrolledEventArgs e)
{
Scrolled?.Invoke(this, e);
OnScrolled(e);
}
public event EventHandler<ScrollToRequestEventArgs> ScrollToRequested;
public event EventHandler<ItemsViewScrolledEventArgs> Scrolled;
public event EventHandler RemainingItemsThresholdReached;
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
// TODO hartez 2018-05-22 05:04 PM This 40,40 is what LV1 does; can we come up with something less arbitrary?
@ -161,5 +207,15 @@ namespace Xamarin.Forms
{
ScrollToRequested?.Invoke(this, e);
}
protected virtual void OnRemainingItemsThresholdReached()
{
}
protected virtual void OnScrolled(ItemsViewScrolledEventArgs e)
{
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using System;
namespace Xamarin.Forms
{
public class ItemsViewScrolledEventArgs : EventArgs
{
public double HorizontalDelta { get; set; }
public double VerticalDelta { get; set; }
public double HorizontalOffset { get; set; }
public double VerticalOffset { get; set; }
public int FirstVisibleItemIndex { get; set; }
public int CenterItemIndex { get; set; }
public int LastVisibleItemIndex { get; set; }
}
}

Просмотреть файл

@ -43,7 +43,7 @@ namespace Xamarin.Forms.Platform.Android
public EmptyViewAdapter(ItemsView itemsView)
{
CollectionView.VerifyCollectionViewFlagEnabled(nameof(EmptyViewAdapter));
Xamarin.Forms.CollectionView.VerifyCollectionViewFlagEnabled(nameof(EmptyViewAdapter));
ItemsView = itemsView;
}

Просмотреть файл

@ -21,7 +21,7 @@ namespace Xamarin.Forms.Platform.Android
internal ItemsViewAdapter(ItemsView itemsView, Func<View, Context, ItemContentView> createItemContentView = null)
{
CollectionView.VerifyCollectionViewFlagEnabled(nameof(ItemsViewAdapter));
Xamarin.Forms.CollectionView.VerifyCollectionViewFlagEnabled(nameof(ItemsViewAdapter));
ItemsView = itemsView;
_createItemContentView = createItemContentView;

Просмотреть файл

@ -1,13 +1,11 @@
using System;
using System.ComponentModel;
using System.Linq;
using Android.Content;
using Android.Graphics;
using Android.Support.V7.Widget;
using Android.Util;
using Android.Views;
using Android.Widget;
using Xamarin.Forms.Internals;
using Xamarin.Forms.Platform.Android.CollectionView;
using Xamarin.Forms.Platform.Android.FastRenderers;
using AViewCompat = Android.Support.V4.View.ViewCompat;
@ -28,6 +26,7 @@ namespace Xamarin.Forms.Platform.Android
IItemsLayout _layout;
SnapManager _snapManager;
ScrollHelper _scrollHelper;
RecyclerViewScrollListener _recyclerViewScrollListener;
EmptyViewAdapter _emptyViewAdapter;
readonly DataChangeObserver _emptyCollectionObserver;
@ -40,7 +39,7 @@ namespace Xamarin.Forms.Platform.Android
public ItemsViewRenderer(Context context) : base(new ContextThemeWrapper(context, Resource.Style.collectionViewStyle))
{
CollectionView.VerifyCollectionViewFlagEnabled(nameof(ItemsViewRenderer));
Xamarin.Forms.CollectionView.VerifyCollectionViewFlagEnabled(nameof(ItemsViewRenderer));
_automationPropertiesProvider = new AutomationPropertiesProvider(this);
_effectControlProvider = new EffectControlProvider(this);
@ -305,6 +304,9 @@ namespace Xamarin.Forms.Platform.Android
// Listen for ScrollTo requests
ItemsView.ScrollToRequested += ScrollToRequested;
_recyclerViewScrollListener = new RecyclerViewScrollListener(ItemsView, ItemsViewAdapter);
AddOnScrollListener(_recyclerViewScrollListener);
}
void UpdateVerticalScrollBarVisibility()
@ -353,6 +355,13 @@ namespace Xamarin.Forms.Platform.Android
// Stop listening for ScrollTo requests
oldElement.ScrollToRequested -= ScrollToRequested;
if (_recyclerViewScrollListener != null)
{
_recyclerViewScrollListener.Dispose();
ClearOnScrollListeners();
_recyclerViewScrollListener = null;
}
if (ItemsViewAdapter != null)
{
// Stop watching for empty items or scroll adjustments

Просмотреть файл

@ -9,7 +9,7 @@ namespace Xamarin.Forms.Platform.Android
public PositionalSmoothScroller(Context context, ScrollToPosition scrollToPosition) : base(context)
{
CollectionView.VerifyCollectionViewFlagEnabled(nameof(PositionalSmoothScroller));
Xamarin.Forms.CollectionView.VerifyCollectionViewFlagEnabled(nameof(PositionalSmoothScroller));
_scrollToPosition = scrollToPosition;
}

Просмотреть файл

@ -0,0 +1,120 @@
using System.Collections.Generic;
using System.Linq;
using Android.Graphics;
using Android.Support.V7.Widget;
namespace Xamarin.Forms.Platform.Android.CollectionView
{
public class RecyclerViewScrollListener : RecyclerView.OnScrollListener
{
bool _disposed;
int _horizontalOffset, _verticalOffset;
ItemsView _itemsView;
ItemsViewAdapter _itemsViewAdapter;
public RecyclerViewScrollListener(ItemsView itemsView, ItemsViewAdapter itemsViewAdapter)
{
_itemsView = itemsView;
_itemsViewAdapter = itemsViewAdapter;
}
public override void OnScrolled(RecyclerView recyclerView, int dx, int dy)
{
base.OnScrolled(recyclerView, dx, dy);
// TODO: These offsets will be incorrect upon row size or count change.
// They are currently provided in place of LayoutManager's default offset calculation
// because it does not report accurate values in the presence of uneven rows.
// See https://stackoverflow.com/questions/27507715/android-how-to-get-the-current-x-offset-of-recyclerview
_horizontalOffset += dx;
_verticalOffset += dy;
var firstVisibleItemIndex = -1;
var lastVisibleItemIndex = -1;
var centerItemIndex = -1;
if (recyclerView.GetLayoutManager() is LinearLayoutManager linearLayoutManager)
{
firstVisibleItemIndex = linearLayoutManager.FindFirstVisibleItemPosition();
lastVisibleItemIndex = linearLayoutManager.FindLastVisibleItemPosition();
centerItemIndex = CalculateCenterItemIndex(firstVisibleItemIndex, lastVisibleItemIndex, linearLayoutManager);
}
var itemsViewScrolledEventArgs = new ItemsViewScrolledEventArgs
{
HorizontalDelta = dx,
VerticalDelta = dy,
HorizontalOffset = _horizontalOffset,
VerticalOffset = _verticalOffset,
FirstVisibleItemIndex = firstVisibleItemIndex,
CenterItemIndex = centerItemIndex,
LastVisibleItemIndex = lastVisibleItemIndex
};
_itemsView.SendScrolled(itemsViewScrolledEventArgs);
// Don't send RemainingItemsThresholdReached event for non-linear layout managers
// This can also happen if a layout pass has not happened yet
if (lastVisibleItemIndex == -1)
return;
switch (_itemsView.RemainingItemsThreshold)
{
case -1:
return;
case 0:
if (lastVisibleItemIndex == _itemsViewAdapter.ItemCount - 1)
_itemsView.SendRemainingItemsThresholdReached();
break;
default:
if (_itemsViewAdapter.ItemCount - 1 - lastVisibleItemIndex <= _itemsView.RemainingItemsThreshold)
_itemsView.SendRemainingItemsThresholdReached();
break;
}
}
static int CalculateCenterItemIndex(int firstVisibleItemIndex, int lastVisibleItemIndex, LinearLayoutManager linearLayoutManager)
{
// This can happen if a layout pass has not happened yet
if (firstVisibleItemIndex == -1)
return firstVisibleItemIndex;
var keyValuePairs = new Dictionary<int, int>();
for (var i = firstVisibleItemIndex; i <= lastVisibleItemIndex; i++)
{
var view = linearLayoutManager.FindViewByPosition(i);
var rect = new Rect();
view.GetLocalVisibleRect(rect);
keyValuePairs[i] = rect.Height();
}
var center = keyValuePairs.Values.Sum() / 2.0;
foreach (var keyValuePair in keyValuePairs)
{
center -= keyValuePair.Value;
if (center <= 0)
return keyValuePair.Key;
}
return firstVisibleItemIndex;
}
protected override void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_itemsView = null;
_itemsViewAdapter = null;
}
_disposed = true;
base.Dispose(disposing);
}
}
}

Просмотреть файл

@ -11,7 +11,7 @@ namespace Xamarin.Forms.Platform.Android
public SnapManager(ItemsView itemsView, RecyclerView recyclerView)
{
CollectionView.VerifyCollectionViewFlagEnabled(nameof(SnapManager));
Xamarin.Forms.CollectionView.VerifyCollectionViewFlagEnabled(nameof(SnapManager));
_recyclerView = recyclerView;
_itemsView = itemsView;
}

Просмотреть файл

@ -72,6 +72,7 @@
<Compile Include="CollectionView\DataChangeObserver.cs" />
<Compile Include="CollectionView\EmptySource.cs" />
<Compile Include="CollectionView\NongreedySnapHelper.cs" />
<Compile Include="CollectionView\RecyclerViewScrollListener.cs" />
<Compile Include="CollectionView\SingleSnapHelper.cs" />
<Compile Include="CollectionView\EmptyViewAdapter.cs" />
<Compile Include="CollectionView\EndSingleSnapHelper.cs" />

Просмотреть файл

@ -9,7 +9,7 @@ namespace Xamarin.Forms.Platform.iOS
// TODO hartez 2018/06/01 14:21:24 Add a method for updating the layout
public class ItemsViewController : UICollectionViewController
{
protected IItemsViewSource ItemsSource { get; set; }
public IItemsViewSource ItemsSource { get; protected set; }
public ItemsView ItemsView { get; }
protected ItemsViewLayout ItemsViewLayout { get; set; }
bool _initialConstraintsSet;

Просмотреть файл

@ -1,4 +1,5 @@
using System;
using System.Linq;
using CoreGraphics;
using Foundation;
using UIKit;
@ -7,17 +8,13 @@ namespace Xamarin.Forms.Platform.iOS
{
public class UICollectionViewDelegator : UICollectionViewDelegateFlowLayout
{
public ItemsViewLayout ItemsViewLayout { get; private set; }
public ItemsViewController ItemsViewController { get; private set; }
public SelectableItemsViewController SelectableItemsViewController
{
get => ItemsViewController as SelectableItemsViewController;
}
float _previousHorizontalOffset, _previousVerticalOffset;
public GroupableItemsViewController GroupableItemsViewController
{
get => ItemsViewController as GroupableItemsViewController;
}
public ItemsViewLayout ItemsViewLayout { get; }
public ItemsViewController ItemsViewController { get; }
public SelectableItemsViewController SelectableItemsViewController => ItemsViewController as SelectableItemsViewController;
public GroupableItemsViewController GroupableItemsViewController => ItemsViewController as GroupableItemsViewController;
public UICollectionViewDelegator(ItemsViewLayout itemsViewLayout, ItemsViewController itemsViewController)
{
@ -25,6 +22,61 @@ namespace Xamarin.Forms.Platform.iOS
ItemsViewController = itemsViewController;
}
public override void DraggingStarted(UIScrollView scrollView)
{
_previousHorizontalOffset = (float)scrollView.ContentOffset.X;
_previousVerticalOffset = (float)scrollView.ContentOffset.Y;
}
public override void DraggingEnded(UIScrollView scrollView, bool willDecelerate)
{
_previousHorizontalOffset = 0;
_previousVerticalOffset = 0;
}
public override void Scrolled(UIScrollView scrollView)
{
var indexPathsForVisibleItems = ItemsViewController.CollectionView.IndexPathsForVisibleItems.OrderBy(x => x.Row).ToList();
if (indexPathsForVisibleItems.Count == 0)
return;
var firstVisibleItemIndex = (int)indexPathsForVisibleItems.First().Item;
var centerPoint = new CGPoint(ItemsViewController.CollectionView.Center.X + ItemsViewController.CollectionView.ContentOffset.X, ItemsViewController.CollectionView.Center.Y + ItemsViewController.CollectionView.ContentOffset.Y);
var centerIndexPath = ItemsViewController.CollectionView.IndexPathForItemAtPoint(centerPoint);
var centerItemIndex = centerIndexPath?.Row ?? firstVisibleItemIndex;
var lastVisibleItemIndex = (int)indexPathsForVisibleItems.Last().Item;
var itemsViewScrolledEventArgs = new ItemsViewScrolledEventArgs
{
HorizontalDelta = scrollView.ContentOffset.X - _previousHorizontalOffset,
VerticalDelta = scrollView.ContentOffset.Y - _previousVerticalOffset,
HorizontalOffset = scrollView.ContentOffset.X,
VerticalOffset = scrollView.ContentOffset.Y,
FirstVisibleItemIndex = firstVisibleItemIndex,
CenterItemIndex = centerItemIndex,
LastVisibleItemIndex = lastVisibleItemIndex
};
ItemsViewController.ItemsView.SendScrolled(itemsViewScrolledEventArgs);
_previousHorizontalOffset = (float)scrollView.ContentOffset.X;
_previousVerticalOffset = (float)scrollView.ContentOffset.Y;
switch (ItemsViewController.ItemsView.RemainingItemsThreshold)
{
case -1:
return;
case 0:
if (lastVisibleItemIndex == ItemsViewController.ItemsSource.ItemCount - 1)
ItemsViewController.ItemsView.SendRemainingItemsThresholdReached();
break;
default:
if (ItemsViewController.ItemsSource.ItemCount - 1 - lastVisibleItemIndex <= ItemsViewController.ItemsView.RemainingItemsThreshold)
ItemsViewController.ItemsView.SendRemainingItemsThresholdReached();
break;
}
}
public override UIEdgeInsets GetInsetForSection(UICollectionView collectionView, UICollectionViewLayout layout,
nint section)
{