зеркало из https://github.com/DeGsoft/maui-linux.git
[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:
Родитель
248e1ce456
Коммит
03afb32d2b
|
@ -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)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче