зеркало из https://github.com/DeGsoft/maui-linux.git
[iOS, Android] Implement Snap alignment for CollectionView (#4414)
* Implement snap alignment for iOS CollectionView * Add missing End/MandatorySingle implementation for Android * Implement MandatorySingle snapping on iOS * Fix issues with Android MandatorySingle skipping items; * Fix rebase issue
This commit is contained in:
Родитель
29074eb045
Коммит
f97fb08263
|
@ -9,9 +9,8 @@
|
|||
var templateLayout = new Grid
|
||||
{
|
||||
RowDefinitions = new RowDefinitionCollection { new RowDefinition(), new RowDefinition() },
|
||||
WidthRequest = 100,
|
||||
HeightRequest = 100,
|
||||
Padding = new Thickness(10)
|
||||
WidthRequest = 200,
|
||||
HeightRequest = 100
|
||||
};
|
||||
|
||||
var image = new Image
|
||||
|
@ -48,15 +47,15 @@
|
|||
var templateLayout = new Grid
|
||||
{
|
||||
RowDefinitions = new RowDefinitionCollection { new RowDefinition(), new RowDefinition {Height = GridLength.Auto} },
|
||||
WidthRequest = 100,
|
||||
HeightRequest = 130
|
||||
WidthRequest = 280,
|
||||
HeightRequest = 310,
|
||||
};
|
||||
|
||||
var image = new Image
|
||||
{
|
||||
Margin = new Thickness(5),
|
||||
HeightRequest = 100,
|
||||
WidthRequest = 100,
|
||||
HeightRequest = 280,
|
||||
WidthRequest = 280,
|
||||
HorizontalOptions = LayoutOptions.Center,
|
||||
VerticalOptions = LayoutOptions.Center,
|
||||
Aspect = Aspect.AspectFit
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
using Android.Support.V7.Widget;
|
||||
using AView = Android.Views.View;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
internal class EndSingleSnapHelper : SingleSnapHelper
|
||||
{
|
||||
public override int[] CalculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, AView targetView)
|
||||
{
|
||||
var orientationHelper = CreateOrientationHelper(layoutManager);
|
||||
var isHorizontal = layoutManager.CanScrollHorizontally();
|
||||
var rtl = isHorizontal && IsLayoutReversed(layoutManager);
|
||||
|
||||
var distance = rtl
|
||||
? -orientationHelper.GetDecoratedStart(targetView)
|
||||
: orientationHelper.TotalSpace - orientationHelper.GetDecoratedEnd(targetView);
|
||||
|
||||
return isHorizontal
|
||||
? new[] { -distance, 1 }
|
||||
: new[] { 1, -distance };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -276,9 +276,10 @@ namespace Xamarin.Forms.Platform.Android
|
|||
UpdateFlowDirection();
|
||||
|
||||
// Keep track of the ItemsLayout's property changes
|
||||
_layout.PropertyChanged += LayoutOnPropertyChanged;
|
||||
|
||||
// TODO hartez 2018/09/17 13:16:12 This propertychanged handler needs to be torn down in Dispose and TearDownElement
|
||||
if (_layout != null)
|
||||
{
|
||||
_layout.PropertyChanged += LayoutOnPropertyChanged;
|
||||
}
|
||||
|
||||
// Listen for ScrollTo requests
|
||||
ItemsView.ScrollToRequested += ScrollToRequested;
|
||||
|
@ -291,6 +292,12 @@ namespace Xamarin.Forms.Platform.Android
|
|||
return;
|
||||
}
|
||||
|
||||
// Stop listening for layout property changes
|
||||
if (_layout != null)
|
||||
{
|
||||
_layout.PropertyChanged -= LayoutOnPropertyChanged;
|
||||
}
|
||||
|
||||
// Stop listening for property changes
|
||||
oldElement.PropertyChanged -= OnElementPropertyChanged;
|
||||
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
using Android.Support.V7.Widget;
|
||||
using AView = Android.Views.View;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
internal class SingleSnapHelper : PagerSnapHelper
|
||||
{
|
||||
// CurrentTargetPosition will have this value until the user scrolls around
|
||||
protected int CurrentTargetPosition = -1;
|
||||
|
||||
protected static OrientationHelper CreateOrientationHelper(RecyclerView.LayoutManager layoutManager)
|
||||
{
|
||||
return layoutManager.CanScrollHorizontally()
|
||||
? OrientationHelper.CreateHorizontalHelper(layoutManager)
|
||||
: OrientationHelper.CreateVerticalHelper(layoutManager);
|
||||
}
|
||||
|
||||
protected static bool IsLayoutReversed(RecyclerView.LayoutManager layoutManager)
|
||||
{
|
||||
if (layoutManager is LinearLayoutManager linearLayoutManager)
|
||||
{
|
||||
return linearLayoutManager.ReverseLayout;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override AView FindSnapView(RecyclerView.LayoutManager layoutManager)
|
||||
{
|
||||
if (layoutManager.ItemCount == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(layoutManager is LinearLayoutManager linearLayoutManager))
|
||||
{
|
||||
// Don't snap to anything if this isn't a LinearLayoutManager;
|
||||
return null;
|
||||
}
|
||||
|
||||
var targetItemPosition = CurrentTargetPosition;
|
||||
|
||||
if (targetItemPosition != -1)
|
||||
{
|
||||
return linearLayoutManager.FindViewByPosition(targetItemPosition);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override int FindTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY)
|
||||
{
|
||||
if (CurrentTargetPosition == -1)
|
||||
{
|
||||
CurrentTargetPosition = base.FindTargetSnapPosition(layoutManager, velocityX, velocityY);
|
||||
return CurrentTargetPosition;
|
||||
}
|
||||
|
||||
var increment = 1;
|
||||
|
||||
if (layoutManager.CanScrollHorizontally())
|
||||
{
|
||||
if (velocityX < 0)
|
||||
{
|
||||
increment = -1;
|
||||
}
|
||||
}
|
||||
else if (layoutManager.CanScrollVertically())
|
||||
{
|
||||
if (velocityY < 0)
|
||||
{
|
||||
increment = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsLayoutReversed(layoutManager))
|
||||
{
|
||||
increment = increment * -1;
|
||||
}
|
||||
|
||||
CurrentTargetPosition = CurrentTargetPosition + increment;
|
||||
|
||||
return CurrentTargetPosition;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -65,11 +65,11 @@ namespace Xamarin.Forms.Platform.Android
|
|||
switch (alignment)
|
||||
{
|
||||
case SnapPointsAlignment.Start:
|
||||
return new StartPagerSnapHelper();
|
||||
return new StartSingleSnapHelper();
|
||||
case SnapPointsAlignment.Center:
|
||||
return new PagerSnapHelper();
|
||||
return new SingleSnapHelper();
|
||||
case SnapPointsAlignment.End:
|
||||
break;
|
||||
return new EndSingleSnapHelper();
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(alignment), alignment, null);
|
||||
}
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
using Android.Support.V7.Widget;
|
||||
using AView = Android.Views.View;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
internal class StartPagerSnapHelper : PagerSnapHelper
|
||||
{
|
||||
protected static OrientationHelper CreateOrientationHelper(RecyclerView.LayoutManager layoutManager)
|
||||
{
|
||||
return layoutManager.CanScrollHorizontally()
|
||||
? OrientationHelper.CreateHorizontalHelper(layoutManager)
|
||||
: OrientationHelper.CreateVerticalHelper(layoutManager);
|
||||
}
|
||||
|
||||
protected static bool IsLayoutReversed(RecyclerView.LayoutManager layoutManager)
|
||||
{
|
||||
if (layoutManager is LinearLayoutManager linearLayoutManager)
|
||||
{
|
||||
return linearLayoutManager.ReverseLayout;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int FindTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY)
|
||||
{
|
||||
var x = base.FindTargetSnapPosition(layoutManager, velocityX, velocityY);
|
||||
return x;
|
||||
}
|
||||
|
||||
public override AView FindSnapView(RecyclerView.LayoutManager layoutManager)
|
||||
{
|
||||
if (layoutManager.ItemCount == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(layoutManager is LinearLayoutManager linearLayoutManager))
|
||||
{
|
||||
// Don't snap to anything if this isn't a LinearLayoutManager;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the first fully visible item
|
||||
var firstVisibleItemPosition = linearLayoutManager.FindFirstCompletelyVisibleItemPosition();
|
||||
|
||||
if (firstVisibleItemPosition == RecyclerView.NoPosition)
|
||||
{
|
||||
// If there are no fully visible items, drop back to default PagerSnapHelper behavior
|
||||
return base.FindSnapView(layoutManager);
|
||||
}
|
||||
|
||||
// Return the view to snap
|
||||
return linearLayoutManager.FindViewByPosition(firstVisibleItemPosition);
|
||||
}
|
||||
|
||||
public override int[] CalculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, AView targetView)
|
||||
{
|
||||
var orientationHelper = CreateOrientationHelper(layoutManager);
|
||||
var isHorizontal = layoutManager.CanScrollHorizontally();
|
||||
var rtl = isHorizontal && IsLayoutReversed(layoutManager);
|
||||
|
||||
var distance = rtl
|
||||
? -orientationHelper.GetDecoratedEnd(targetView)
|
||||
: orientationHelper.GetDecoratedStart(targetView);
|
||||
|
||||
return isHorizontal
|
||||
? new[] { distance, 1 }
|
||||
: new[] { 1, distance };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using Android.Support.V7.Widget;
|
||||
using AView = Android.Views.View;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
internal class StartSingleSnapHelper : SingleSnapHelper
|
||||
{
|
||||
public override int[] CalculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, AView targetView)
|
||||
{
|
||||
var orientationHelper = CreateOrientationHelper(layoutManager);
|
||||
var isHorizontal = layoutManager.CanScrollHorizontally();
|
||||
var rtl = isHorizontal && IsLayoutReversed(layoutManager);
|
||||
|
||||
var distance = rtl
|
||||
? -orientationHelper.GetDecoratedEnd(targetView)
|
||||
: orientationHelper.GetDecoratedStart(targetView);
|
||||
|
||||
return isHorizontal
|
||||
? new[] { distance, 1 }
|
||||
: new[] { 1, distance };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -71,7 +71,9 @@
|
|||
<Compile Include="CollectionView\CarouselViewRenderer.cs" />
|
||||
<Compile Include="CollectionView\DataChangeObserver.cs" />
|
||||
<Compile Include="CollectionView\EmptySource.cs" />
|
||||
<Compile Include="CollectionView\SingleSnapHelper.cs" />
|
||||
<Compile Include="CollectionView\EmptyViewAdapter.cs" />
|
||||
<Compile Include="CollectionView\EndSingleSnapHelper.cs" />
|
||||
<Compile Include="CollectionView\ItemsViewAdapter.cs" />
|
||||
<Compile Include="CollectionView\EdgeSnapHelper.cs" />
|
||||
<Compile Include="CollectionView\EndSnapHelper.cs" />
|
||||
|
@ -89,7 +91,7 @@
|
|||
<Compile Include="CollectionView\SelectableViewHolder.cs" />
|
||||
<Compile Include="CollectionView\SizedItemContentView.cs" />
|
||||
<Compile Include="CollectionView\SnapManager.cs" />
|
||||
<Compile Include="CollectionView\StartPagerSnapHelper.cs" />
|
||||
<Compile Include="CollectionView\StartSingleSnapHelper.cs" />
|
||||
<Compile Include="CollectionView\StartSnapHelper.cs" />
|
||||
<Compile Include="CollectionView\TemplatedItemViewHolder.cs" />
|
||||
<Compile Include="CollectionView\TextViewHolder.cs" />
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using CoreGraphics;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
|
|
|
@ -18,7 +18,8 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
public class CollectionViewRenderer : ViewRenderer<CollectionView, UIView>
|
||||
{
|
||||
CollectionViewController _collectionViewController;
|
||||
ItemsViewLayout _layout;
|
||||
ItemsViewLayout _flowLayout;
|
||||
IItemsLayout _layout;
|
||||
bool _disposed;
|
||||
|
||||
public CollectionViewRenderer()
|
||||
|
@ -89,11 +90,12 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
return;
|
||||
}
|
||||
|
||||
_layout = SelectLayout(newElement.ItemsLayout);
|
||||
_collectionViewController = new CollectionViewController(newElement, _layout);
|
||||
_layout = newElement.ItemsLayout;
|
||||
_flowLayout = SelectLayout(_layout);
|
||||
_collectionViewController = new CollectionViewController(newElement, _flowLayout);
|
||||
SetNativeControl(_collectionViewController.View);
|
||||
_collectionViewController.CollectionView.BackgroundColor = UIColor.Clear;
|
||||
_collectionViewController.CollectionView.WeakDelegate = _layout;
|
||||
_collectionViewController.CollectionView.WeakDelegate = _flowLayout;
|
||||
_collectionViewController.UpdateEmptyView();
|
||||
|
||||
// Listen for ScrollTo requests
|
||||
|
@ -123,7 +125,7 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
}
|
||||
|
||||
_collectionViewController.CollectionView.ScrollToItem(indexPath,
|
||||
args.ScrollToPosition.ToCollectionViewScrollPosition(_layout.ScrollDirection),
|
||||
args.ScrollToPosition.ToCollectionViewScrollPosition(_flowLayout.ScrollDirection),
|
||||
args.IsAnimated);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using CoreGraphics;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using Xamarin.Forms.Internals;
|
||||
|
||||
namespace Xamarin.Forms.Platform.iOS
|
||||
{
|
||||
|
@ -53,7 +55,7 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
HandlePropertyChanged(propertyChanged);
|
||||
}
|
||||
|
||||
protected virtual void HandlePropertyChanged(PropertyChangedEventArgs propertyChanged)
|
||||
protected virtual void HandlePropertyChanged(PropertyChangedEventArgs propertyChanged)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -215,5 +217,102 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
{
|
||||
_needCellSizeUpdate = true;
|
||||
}
|
||||
|
||||
public override CGPoint TargetContentOffset(CGPoint proposedContentOffset, CGPoint scrollingVelocity)
|
||||
{
|
||||
var snapPointsType = _itemsLayout.SnapPointsType;
|
||||
|
||||
if (snapPointsType == SnapPointsType.None)
|
||||
{
|
||||
// Nothing to do here; fall back to the default
|
||||
return base.TargetContentOffset(proposedContentOffset, scrollingVelocity);
|
||||
}
|
||||
|
||||
var alignment = _itemsLayout.SnapPointsAlignment;
|
||||
|
||||
if (snapPointsType == SnapPointsType.MandatorySingle)
|
||||
{
|
||||
// Mandatory snapping, single element
|
||||
return ScrollSingle(alignment, proposedContentOffset, scrollingVelocity);
|
||||
}
|
||||
|
||||
// Get the viewport of the UICollectionView at the proposed content offset
|
||||
var viewport = new CGRect(proposedContentOffset, CollectionView.Bounds.Size);
|
||||
|
||||
// And find all the elements currently visible in the viewport
|
||||
var visibleElements = LayoutAttributesForElementsInRect(viewport);
|
||||
|
||||
if (visibleElements.Length == 0)
|
||||
{
|
||||
// Nothing to see here; fall back to the default
|
||||
return base.TargetContentOffset(proposedContentOffset, scrollingVelocity);
|
||||
}
|
||||
|
||||
if (visibleElements.Length == 1)
|
||||
{
|
||||
// If there is only one item in the viewport, then we need to align the viewport with it
|
||||
return SnapHelpers.AdjustContentOffset(proposedContentOffset, visibleElements[0].Frame, viewport,
|
||||
alignment, ScrollDirection);
|
||||
}
|
||||
|
||||
// If there are multiple items in the viewport, we need to choose the one which is
|
||||
// closest to the relevant part of the viewport while being sufficiently visible
|
||||
|
||||
// Find the spot in the viewport we're trying to align with
|
||||
var alignmentTarget = SnapHelpers.FindAlignmentTarget(alignment, proposedContentOffset,
|
||||
CollectionView, ScrollDirection);
|
||||
|
||||
// Find the closest sufficiently visible candidate
|
||||
var bestCandidate = SnapHelpers.FindBestSnapCandidate(visibleElements, viewport, alignmentTarget);
|
||||
|
||||
if (bestCandidate != null)
|
||||
{
|
||||
return SnapHelpers.AdjustContentOffset(proposedContentOffset, bestCandidate.Frame, viewport, alignment,
|
||||
ScrollDirection);
|
||||
}
|
||||
|
||||
// If we got this far an nothing matched, it means that we have multiple items but somehow
|
||||
// none of them fit at least half in the viewport. So just fall back to the first item
|
||||
return SnapHelpers.AdjustContentOffset(proposedContentOffset, visibleElements[0].Frame, viewport, alignment,
|
||||
ScrollDirection);
|
||||
}
|
||||
|
||||
CGPoint ScrollSingle(SnapPointsAlignment alignment, CGPoint proposedContentOffset, CGPoint scrollingVelocity)
|
||||
{
|
||||
// Get the viewport of the UICollectionView at the current content offset
|
||||
var contentOffset = CollectionView.ContentOffset;
|
||||
var viewport = new CGRect(contentOffset, CollectionView.Bounds.Size);
|
||||
|
||||
// Find the spot in the viewport we're trying to align with
|
||||
var alignmentTarget = SnapHelpers.FindAlignmentTarget(alignment, contentOffset, CollectionView, ScrollDirection);
|
||||
|
||||
var visibleElements = LayoutAttributesForElementsInRect(viewport);
|
||||
|
||||
// Find the current aligned item
|
||||
var currentItem = SnapHelpers.FindBestSnapCandidate(visibleElements, viewport, alignmentTarget);
|
||||
|
||||
if (currentItem == null)
|
||||
{
|
||||
// Somehow we don't currently have an item in the viewport near the target; fall back to the
|
||||
// default behavior
|
||||
return base.TargetContentOffset(proposedContentOffset, scrollingVelocity);
|
||||
}
|
||||
|
||||
// Determine the index of the current item
|
||||
var currentIndex = visibleElements.IndexOf(currentItem);
|
||||
|
||||
// Figure out the step size when jumping to the "next" element
|
||||
var span = 1;
|
||||
if (_itemsLayout is GridItemsLayout gridItemsLayout)
|
||||
{
|
||||
span = gridItemsLayout.Span;
|
||||
}
|
||||
|
||||
// Find the next item in the
|
||||
currentItem = SnapHelpers.FindNextItem(visibleElements, ScrollDirection, span, scrollingVelocity, currentIndex);
|
||||
|
||||
return SnapHelpers.AdjustContentOffset(CollectionView.ContentOffset, currentItem.Frame, viewport, alignment,
|
||||
ScrollDirection);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
using System;
|
||||
using CoreGraphics;
|
||||
using UIKit;
|
||||
|
||||
namespace Xamarin.Forms.Platform.iOS
|
||||
{
|
||||
internal class SnapHelpers
|
||||
{
|
||||
public static CGPoint AdjustContentOffset(CGPoint proposedContentOffset, CGRect itemFrame,
|
||||
CGRect viewport, SnapPointsAlignment alignment, UICollectionViewScrollDirection scrollDirection)
|
||||
{
|
||||
var offset = GetViewportOffset(itemFrame, viewport, alignment, scrollDirection);
|
||||
return new CGPoint(proposedContentOffset.X - offset.X, proposedContentOffset.Y - offset.Y);
|
||||
}
|
||||
|
||||
public static CGPoint FindAlignmentTarget(SnapPointsAlignment snapPointsAlignment,
|
||||
CGPoint contentOffset, UICollectionView collectionView, UICollectionViewScrollDirection scrollDirection)
|
||||
{
|
||||
var inset = collectionView.ContentInset;
|
||||
var bounds = collectionView.Bounds;
|
||||
|
||||
switch (scrollDirection)
|
||||
{
|
||||
case UICollectionViewScrollDirection.Vertical:
|
||||
var y = FindAlignmentTarget(snapPointsAlignment, contentOffset.Y, inset.Top,
|
||||
contentOffset.Y + bounds.Height, inset.Bottom);
|
||||
return new CGPoint(contentOffset.X, y);
|
||||
case UICollectionViewScrollDirection.Horizontal:
|
||||
var x = FindAlignmentTarget(snapPointsAlignment, contentOffset.X, inset.Left,
|
||||
contentOffset.X + bounds.Width, inset.Right);
|
||||
return new CGPoint(x, contentOffset.Y);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public static UICollectionViewLayoutAttributes FindBestSnapCandidate(UICollectionViewLayoutAttributes[] items,
|
||||
CGRect viewport, CGPoint alignmentTarget)
|
||||
{
|
||||
UICollectionViewLayoutAttributes bestCandidate = null;
|
||||
|
||||
foreach (UICollectionViewLayoutAttributes item in items)
|
||||
{
|
||||
if (!IsAtLeastHalfVisible(item, viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bestCandidate = bestCandidate == null ? item : Nearer(bestCandidate, item, alignmentTarget);
|
||||
}
|
||||
|
||||
return bestCandidate;
|
||||
}
|
||||
|
||||
static nfloat Area(CGRect rect)
|
||||
{
|
||||
return rect.Height * rect.Width;
|
||||
}
|
||||
|
||||
static CGPoint Center(CGRect rect)
|
||||
{
|
||||
return new CGPoint(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
|
||||
}
|
||||
|
||||
static nfloat DistanceSquared(CGRect rect, CGPoint target)
|
||||
{
|
||||
var rectCenter = Center(rect);
|
||||
|
||||
return (target.X - rectCenter.X) * (target.X - rectCenter.X) +
|
||||
(target.Y - rectCenter.Y) * (target.Y - rectCenter.Y);
|
||||
}
|
||||
|
||||
static int Clamp(int n, int min, int max)
|
||||
{
|
||||
if (n < min)
|
||||
{
|
||||
return min;
|
||||
}
|
||||
|
||||
if (n > max)
|
||||
{
|
||||
return max;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static nfloat FindAlignmentTarget(SnapPointsAlignment snapPointsAlignment, nfloat start, nfloat startInset,
|
||||
nfloat end, nfloat endInset)
|
||||
{
|
||||
switch (snapPointsAlignment)
|
||||
{
|
||||
case SnapPointsAlignment.Center:
|
||||
var viewPortStart = start + startInset;
|
||||
var viewPortEnd = end - endInset;
|
||||
var viewPortSize = viewPortEnd - viewPortStart;
|
||||
|
||||
return viewPortStart + (viewPortSize / 2);
|
||||
|
||||
case SnapPointsAlignment.End:
|
||||
return end - endInset;
|
||||
|
||||
case SnapPointsAlignment.Start:
|
||||
default:
|
||||
return start + startInset;
|
||||
}
|
||||
}
|
||||
|
||||
static CGPoint GetViewportOffset(CGRect itemFrame, CGRect viewport, SnapPointsAlignment snapPointsAlignment,
|
||||
UICollectionViewScrollDirection scrollDirection)
|
||||
{
|
||||
if (scrollDirection == UICollectionViewScrollDirection.Horizontal)
|
||||
{
|
||||
if (snapPointsAlignment == SnapPointsAlignment.Start)
|
||||
{
|
||||
return new CGPoint(viewport.Left - itemFrame.Left, 0);
|
||||
}
|
||||
|
||||
if (snapPointsAlignment == SnapPointsAlignment.End)
|
||||
{
|
||||
return new CGPoint(viewport.Right - itemFrame.Right, 0);
|
||||
}
|
||||
|
||||
var centerViewport = Center(viewport);
|
||||
var centerItem = Center(itemFrame);
|
||||
|
||||
return new CGPoint(centerViewport.X - centerItem.X, 0);
|
||||
}
|
||||
|
||||
if (snapPointsAlignment == SnapPointsAlignment.Start)
|
||||
{
|
||||
return new CGPoint(0, viewport.Top - itemFrame.Top);
|
||||
}
|
||||
|
||||
if (snapPointsAlignment == SnapPointsAlignment.End)
|
||||
{
|
||||
return new CGPoint(0, viewport.Bottom - itemFrame.Bottom);
|
||||
}
|
||||
|
||||
var centerViewport1 = Center(viewport);
|
||||
var centerItem1 = Center(itemFrame);
|
||||
|
||||
return new CGPoint(0, centerViewport1.Y - centerItem1.Y);
|
||||
}
|
||||
|
||||
static bool IsAtLeastHalfVisible(UICollectionViewLayoutAttributes item, CGRect viewport)
|
||||
{
|
||||
var itemFrame = item.Frame;
|
||||
var visibleArea = Area(CGRect.Intersect(itemFrame, viewport));
|
||||
|
||||
return visibleArea >= Area(itemFrame) / 2;
|
||||
}
|
||||
|
||||
static UICollectionViewLayoutAttributes Nearer(UICollectionViewLayoutAttributes a,
|
||||
UICollectionViewLayoutAttributes b,
|
||||
CGPoint target)
|
||||
{
|
||||
var dA = DistanceSquared(a.Frame, target);
|
||||
var dB = DistanceSquared(b.Frame, target);
|
||||
|
||||
if (dA < dB)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
public static UICollectionViewLayoutAttributes FindNextItem(UICollectionViewLayoutAttributes[] items,
|
||||
UICollectionViewScrollDirection direction, int step, CGPoint scrollingVelocity, int currentIndex)
|
||||
{
|
||||
var velocity = direction == UICollectionViewScrollDirection.Horizontal
|
||||
? scrollingVelocity.X
|
||||
: scrollingVelocity.Y;
|
||||
|
||||
if (velocity == 0)
|
||||
{
|
||||
// The user isn't scrolling at all, just stay where we are
|
||||
return items[currentIndex];
|
||||
}
|
||||
|
||||
// Move the index up or down by increment, depending on the velocity
|
||||
if (velocity > 0)
|
||||
{
|
||||
currentIndex = currentIndex + step;
|
||||
}
|
||||
else if (velocity < 0)
|
||||
{
|
||||
currentIndex = currentIndex - step;
|
||||
}
|
||||
|
||||
// Make sure we're not out of bounds
|
||||
currentIndex = Clamp(currentIndex, 0, items.Length - 1);
|
||||
|
||||
return items[currentIndex];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -129,6 +129,7 @@
|
|||
<Compile Include="CollectionView\ListViewLayout.cs" />
|
||||
<Compile Include="CollectionView\ObservableItemsSource.cs" />
|
||||
<Compile Include="CollectionView\PropertyChangedEventArgsExtensions.cs" />
|
||||
<Compile Include="CollectionView\SnapHelpers.cs" />
|
||||
<Compile Include="CollectionView\TemplatedCell.cs" />
|
||||
<Compile Include="CollectionView\HorizontalTemplatedCell.cs" />
|
||||
<Compile Include="CollectionView\VerticalTemplatedCell.cs" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче