From bde1b852c99d25bcc48b4470d735385a27d39393 Mon Sep 17 00:00:00 2001 From: "E.Z. Hart" Date: Fri, 20 Sep 2019 16:03:55 -0600 Subject: [PATCH] Finish ScrollTo implementations for CollectionView on UWP (#7509) partially implements #3172 * Finish ScrollTo implementations for CollectionView on UWP * Fix NRE when attempting to scroll to index greater than source length --- .../CollectionView/ItemContentControl.cs | 9 +- .../CollectionView/ItemsViewRenderer.cs | 179 +++------- .../CollectionView/ItemsViewStyles.xaml | 8 +- .../CollectionView/ScrollHelpers.cs | 338 ++++++++++++++++++ .../Xamarin.Forms.Platform.UAP.csproj | 1 + 5 files changed, 396 insertions(+), 139 deletions(-) create mode 100644 Xamarin.Forms.Platform.UAP/CollectionView/ScrollHelpers.cs diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs index 00f20cc055e..6533dd7d020 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemContentControl.cs @@ -119,6 +119,13 @@ internal void Realize() var formsTemplate = FormsDataTemplate; var container = FormsContainer; + var itemsView = container as ItemsView; + + if (itemsView != null && _renderer?.Element != null) + { + itemsView.RemoveLogicalChild(_renderer.Element); + } + if (dataContext == null || formsTemplate == null || container == null) { return; @@ -131,7 +138,7 @@ internal void Realize() Content = _renderer.ContainerElement; - // TODO ezhart Add View as a logical child of the ItemsView + itemsView?.AddLogicalChild(view); BindableObject.SetInheritedBindingContext(_renderer.Element, dataContext); } diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs index 1857f6b6cc7..f1333b057f6 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewRenderer.cs @@ -199,137 +199,6 @@ protected virtual void TearDownOldElement(ItemsView oldElement) oldElement.ScrollToRequested -= ScrollToRequested; } - async void ScrollToRequested(object sender, ScrollToRequestEventArgs args) - { - await ScrollTo(args); - } - - object FindBoundItem(ScrollToRequestEventArgs args) - { - if (args.Mode == ScrollToMode.Position) - { - return _collectionViewSource.View[args.Index]; - } - - if (Element.ItemTemplate == null) - { - return args.Item; - } - - for (int n = 0; n < _collectionViewSource.View.Count; n++) - { - if (_collectionViewSource.View[n] is ItemTemplateContext pair) - { - if (pair.Item == args.Item) - { - return _collectionViewSource.View[n]; - } - } - } - - return null; - } - - async Task JumpTo(ListViewBase list, object targetItem, ScrollToPosition scrollToPosition) - { - var tcs = new TaskCompletionSource(); - void ViewChanged(object s, ScrollViewerViewChangedEventArgs e) => tcs.TrySetResult(null); - var scrollViewer = list.GetFirstDescendant(); - - try - { - scrollViewer.ViewChanged += ViewChanged; - - if (scrollToPosition == ScrollToPosition.Start) - { - list.ScrollIntoView(targetItem, ScrollIntoViewAlignment.Leading); - } - else if (scrollToPosition == ScrollToPosition.MakeVisible) - { - list.ScrollIntoView(targetItem, ScrollIntoViewAlignment.Default); - } - else - { - // Center and End are going to be more complicated. - } - - await tcs.Task; - } - finally - { - scrollViewer.ViewChanged -= ViewChanged; - } - - } - - async Task ChangeViewAsync(ScrollViewer scrollViewer, double? horizontalOffset, double? verticalOffset, bool disableAnimation) - { - var tcs = new TaskCompletionSource(); - void ViewChanged(object s, ScrollViewerViewChangedEventArgs e) => tcs.TrySetResult(null); - - try - { - scrollViewer.ViewChanged += ViewChanged; - scrollViewer.ChangeView(horizontalOffset, verticalOffset, null, disableAnimation); - await tcs.Task; - } - finally - { - scrollViewer.ViewChanged -= ViewChanged; - } - } - - async Task AnimateTo(ListViewBase list, object targetItem, ScrollToPosition scrollToPosition) - { - var scrollViewer = list.GetFirstDescendant(); - - var targetContainer = list.ContainerFromItem(targetItem) as UIElement; - - if (targetContainer == null) - { - var horizontalOffset = scrollViewer.HorizontalOffset; - var verticalOffset = scrollViewer.VerticalOffset; - - await JumpTo(list, targetItem, scrollToPosition); - targetContainer = list.ContainerFromItem(targetItem) as UIElement; - await ChangeViewAsync(scrollViewer, horizontalOffset, verticalOffset, true); - } - - if (targetContainer == null) - { - // Did not find the target item anywhere - return; - } - - // TODO hartez 2018/10/04 16:37:35 Okay, this sort of works for vertical lists but fails totally on horizontal lists. - var transform = targetContainer.TransformToVisual(scrollViewer.Content as UIElement); - var position = transform?.TransformPoint(new Windows.Foundation.Point(0, 0)); - - if (!position.HasValue) - { - return; - } - - // TODO hartez 2018/10/05 17:23:23 The animated scroll works fine vertically if we are scrolling to a greater Y offset. - // If we're scrolling back up to a lower Y offset, it just gives up and sends us to 0 (first item) - // Works fine if we disable animation, but that's not very helpful - - scrollViewer.ChangeView(position.Value.X, position.Value.Y, null, false); - - //if (scrollToPosition == ScrollToPosition.End) - //{ - // // Modify position - //} - //else if (scrollToPosition == ScrollToPosition.Center) - //{ - // // Modify position - //} - //else - //{ - - //} - } - void UpdateVerticalScrollBarVisibility() { if (_defaultVerticalScrollVisibility == null) @@ -375,18 +244,60 @@ protected virtual async Task ScrollTo(ScrollToRequestEventArgs args) return; } - var targetItem = FindBoundItem(args); + var item = FindBoundItem(args); + + if (item == null) + { + // Item wasn't found in the list, so there's nothing to scroll to + return; + } if (args.IsAnimated) { - await AnimateTo(list, targetItem, args.ScrollToPosition); + await ScrollHelpers.AnimateToItemAsync(list, item, args.ScrollToPosition); } else { - await JumpTo(list, targetItem, args.ScrollToPosition); + await ScrollHelpers.JumpToItemAsync(list, item, args.ScrollToPosition); } } + async void ScrollToRequested(object sender, ScrollToRequestEventArgs args) + { + await ScrollTo(args); + } + + object FindBoundItem(ScrollToRequestEventArgs args) + { + if (args.Mode == ScrollToMode.Position) + { + if (args.Index >= _collectionViewSource.View.Count) + { + return null; + } + + return _collectionViewSource.View[args.Index]; + } + + if (Element.ItemTemplate == null) + { + return args.Item; + } + + for (int n = 0; n < _collectionViewSource.View.Count; n++) + { + if (_collectionViewSource.View[n] is ItemTemplateContext pair) + { + if (pair.Item == args.Item) + { + return _collectionViewSource.View[n]; + } + } + } + + return null; + } + protected virtual void UpdateEmptyView() { if (Element == null || ListViewBase == null) diff --git a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml index c74b732e32e..69f851059c0 100644 --- a/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml +++ b/Xamarin.Forms.Platform.UAP/CollectionView/ItemsViewStyles.xaml @@ -2,10 +2,10 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Xamarin.Forms.Platform.UWP"> - - - - + + + +