From 7b6426175d3d2263747ee33afa7eceba22c20033 Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Mon, 23 Apr 2018 15:29:45 +0200 Subject: [PATCH 01/14] replaced container frame with transparent StackLayout --- SwipeCards.Controls/Views/CardView.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SwipeCards.Controls/Views/CardView.xaml b/SwipeCards.Controls/Views/CardView.xaml index 463381b..50f2228 100644 --- a/SwipeCards.Controls/Views/CardView.xaml +++ b/SwipeCards.Controls/Views/CardView.xaml @@ -5,7 +5,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:effects="clr-namespace:SwipeCards.Demo.Forms.Effects"> - + - + From bbd56f575e693f5fa3e42bd7ff93ab48d8d5d995 Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Fri, 4 May 2018 16:16:11 +0200 Subject: [PATCH 02/14] added dragging event various fixes --- .../Arguments/DraggingEventArgs.cs | 20 +- .../Arguments/SwipedEventArgs.cs | 31 +- .../SwipeCards.Controls.csproj | 4 +- SwipeCards.Controls/Views/CardStackView.xaml | 19 +- .../Views/CardStackView.xaml.cs | 690 ++++++++---------- SwipeCards.Controls/Views/CardView.xaml | 10 +- SwipeCards.Controls/Views/CardView.xaml.cs | 35 +- 7 files changed, 367 insertions(+), 442 deletions(-) diff --git a/SwipeCards.Controls/Arguments/DraggingEventArgs.cs b/SwipeCards.Controls/Arguments/DraggingEventArgs.cs index 60d1a59..5679588 100644 --- a/SwipeCards.Controls/Arguments/DraggingEventArgs.cs +++ b/SwipeCards.Controls/Arguments/DraggingEventArgs.cs @@ -1,14 +1,16 @@ using System; -namespace SwipeCards.Controls.Arguments +namespace SwipeCards { - public class DraggingEventArgs : EventArgs - { - public readonly object Item; + public class DraggingEventArgs : EventArgs + { + public object Item { get; private set; } + public double Distance { get; private set; } - public DraggingEventArgs(object item) - { - this.Item = item; - } - } + public DraggingEventArgs(object item, double distance) + { + Item = item; + Distance = distance; + } + } } diff --git a/SwipeCards.Controls/Arguments/SwipedEventArgs.cs b/SwipeCards.Controls/Arguments/SwipedEventArgs.cs index 20814bc..27592ab 100644 --- a/SwipeCards.Controls/Arguments/SwipedEventArgs.cs +++ b/SwipeCards.Controls/Arguments/SwipedEventArgs.cs @@ -1,21 +1,22 @@ using System; -namespace SwipeCards.Controls.Arguments +namespace SwipeCards { - public class SwipedEventArgs : EventArgs - { - public readonly object Item; - public readonly SwipeDirection Direction; + public class SwipedEventArgs : EventArgs + { + public object Item { get; private set; } + public SwipeDirection Direction { get; private set; } - public SwipedEventArgs(object item, SwipeDirection direction) - { - this.Item = item; - this.Direction = direction; - } - } + public SwipedEventArgs(object item, SwipeDirection direction) + { + Item = item; + Direction = direction; + } + } - public enum SwipeDirection - { - Left, Right - } + public enum SwipeDirection + { + Left, + Right + } } diff --git a/SwipeCards.Controls/SwipeCards.Controls.csproj b/SwipeCards.Controls/SwipeCards.Controls.csproj index 6de94d1..c02e25b 100644 --- a/SwipeCards.Controls/SwipeCards.Controls.csproj +++ b/SwipeCards.Controls/SwipeCards.Controls.csproj @@ -34,8 +34,8 @@ - - + + \ No newline at end of file diff --git a/SwipeCards.Controls/Views/CardStackView.xaml b/SwipeCards.Controls/Views/CardStackView.xaml index 0ea6d56..b85d0e5 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml +++ b/SwipeCards.Controls/Views/CardStackView.xaml @@ -1,21 +1,6 @@ - + - - - - - - + diff --git a/SwipeCards.Controls/Views/CardStackView.xaml.cs b/SwipeCards.Controls/Views/CardStackView.xaml.cs index 3025312..0ea9289 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml.cs +++ b/SwipeCards.Controls/Views/CardStackView.xaml.cs @@ -1,382 +1,322 @@ using System; -using System.Collections.Generic; using Xamarin.Forms; using System.Collections; using System.Threading.Tasks; using System.Windows.Input; -using System.Collections.ObjectModel; using System.Collections.Specialized; -using Xamarin.Forms.Internals; -using SwipeCards.Controls.Arguments; -using Xamarin.Forms.Xaml; -using System.Reflection; - -namespace SwipeCards.Controls -{ - public partial class CardStackView : ContentView - { - #region ItemsSource Property - - public static readonly BindableProperty ItemsSourceProperty = - BindableProperty.Create( - nameof(ItemsSource), typeof(IList), - typeof(CardStackView), - null, - BindingMode.TwoWay, - propertyChanged: OnItemsSourcePropertyChanged); - - private static NotifyCollectionChangedEventHandler CollectionChangedEventHandler; - private static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue) - { - // (Re-)subscribe to source changes - if (newValue is INotifyCollectionChanged) - { - // If ItemSource is INotifyCollectionChanged, it can notify us about collection changes - // In this case, we can use this, as a trigger for Setup() - - // Unsubscibe before - if (CollectionChangedEventHandler != null) - ((INotifyCollectionChanged)newValue).CollectionChanged -= CollectionChangedEventHandler; - - // Subscribe event handler - CollectionChangedEventHandler = (sender, e) => ItemsSource_CollectionChanged(sender, e, (CardStackView)bindable); - ((INotifyCollectionChanged)newValue).CollectionChanged += CollectionChangedEventHandler; - } - - // Even if ItemsSource is not INotifyCollectionChanged, we need to - // call Setup() whenever the whole collection changes - ((CardStackView)bindable).Setup(); - } - - static void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e, CardStackView cardStackView) - { - cardStackView.Setup(); - } - - public IList ItemsSource - { - get { return (IList)GetValue(ItemsSourceProperty); } - set { SetValue(ItemsSourceProperty, value); } - } - - #endregion - - #region ItemTemplate Property - - public static readonly BindableProperty ItemTemplateProperty = - BindableProperty.Create( - nameof(ItemTemplate), - typeof(DataTemplate), - typeof(CardStackView), - new DataTemplate(() => - { - var label = new Label { VerticalOptions = LayoutOptions.Center, HorizontalOptions = LayoutOptions.Center }; - label.SetBinding(Label.TextProperty, "Binding"); - return new ViewCell { View = label }; - }), - propertyChanged: OnItemTemplatePropertyChanged); - - private static void OnItemTemplatePropertyChanged(BindableObject bindable, object oldValue, object newValue) - { - ((CardStackView)bindable).Setup(); - } - - public DataTemplate ItemTemplate - { - get { return (DataTemplate)GetValue(ItemTemplateProperty); } - set { SetValue(ItemTemplateProperty, value); } - } - - #endregion - - #region Misc Properties - - public static readonly BindableProperty CardMoveDistanceProperty = BindableProperty.Create(nameof(CardMoveDistance), typeof(int), typeof(CardStackView), -1); - - /// - /// Distance, that a card has to be dragged into one direction to trigger the flip - /// - /// The card move distance. - public int CardMoveDistance - { - get { return (int)GetValue(CardMoveDistanceProperty); } - set { SetValue(CardMoveDistanceProperty, value); } - } - - public static BindableProperty SwipedRightCommandProperty = BindableProperty.Create(nameof(SwipedRightCommand), typeof(ICommand), typeof(CardStackView), null); - public ICommand SwipedRightCommand - { - get { return (ICommand)GetValue(SwipedRightCommandProperty); } - set { SetValue(SwipedRightCommandProperty, value); } - } - - public static BindableProperty SwipedLeftCommandProperty = BindableProperty.Create(nameof(SwipedLeftCommand), typeof(ICommand), typeof(CardStackView), null); - public ICommand SwipedLeftCommand - { - get { return (ICommand)GetValue(SwipedLeftCommandProperty); } - set { SetValue(SwipedLeftCommandProperty, value); } - } - - //public static readonly BindableProperty HasShadowProperty = BindableProperty.Create(nameof(HasShadow), typeof(bool), typeof(CardStackView), false); - //public bool HasShadow - //{ - // get { return (bool)GetValue(HasShadowProperty); } - // set { SetValue(HasShadowProperty, value); } - //} - - #endregion - - public event EventHandler Swiped; - public event EventHandler StartedDragging; - public event EventHandler FinishedDragging; - - private const int numberOfCards = 2; - private const int defaultAnimationLength = 250; - private float defaultSubcardScale = 0.8f; - private float cardDistance = 0; - private int itemIndex = 0; - - public CardStackView() - { - InitializeComponent(); - - // Register pan gesture - var panGesture = new PanGestureRecognizer(); - panGesture.PanUpdated += OnPanUpdated; - TouchObserber.GestureRecognizers.Add(panGesture); - - Setup(); - } - - public void Setup() - { - // TODO: Reduce Setup() calls - // When starting the app, Setup() gets called multiple times (OnItemsSourcePropertyChanged, OnItemTemplatePropertyChanged, ...). Try to reduce that to 1 - - // Reset CardStack first - CardStack.Children.Clear(); - - // Add two cards (one for the front, one for the background) to the stack - // Use inverse direction to ensure that first card is on top - for (var i = numberOfCards - 1; i >= 0; i--) - { - // Create CardView - var cardView = new CardView(ItemTemplate) - { - IsVisible = false, - Scale = (i == 0) ? 1.0f : defaultSubcardScale, - IsEnabled = false - }; - - // Add CardView to UI - CardStack.Children.Add( - cardView, - Constraint.Constant(0), // X - Constraint.Constant(0), // Y - Constraint.RelativeToParent((parent) => { return parent.Width; }), // Width - Constraint.RelativeToParent((parent) => { return parent.Height; }) // Height - ); - } - - // Reset item index - itemIndex = 0; - - // Start displaying card content - ShowNextCard(); - } - - protected override void OnSizeAllocated(double width, double height) - { - base.OnSizeAllocated(width, height); - - // Recalculate move distance - // When not set differently, this distance is 1/3 of the control's width - if (CardMoveDistance == -1 && !width.Equals(-1)) - CardMoveDistance = (int)(width / 3); - } - - #region Handle Touch Swiping - - async void OnPanUpdated(object sender, PanUpdatedEventArgs e) - { - switch (e.StatusType) - { - case GestureStatus.Started: - HandleTouchStart(); - break; - case GestureStatus.Running: - HandleTouchRunning((float)e.TotalX); - break; - case GestureStatus.Completed: - await HandleTouchCompleted(); - break; - case GestureStatus.Canceled: - break; - } - } - - void HandleTouchStart() - { - if (itemIndex < ItemsSource.Count) - StartedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[itemIndex])); - } - - void HandleTouchRunning(float xDiff) - { - if (itemIndex >= ItemsSource.Count) - return; - - var topCard = CardStack.Children[numberOfCards - 1]; - var backCard = CardStack.Children[numberOfCards - 2]; - - // Move the top card - if (topCard.IsVisible) - { - // Move the card - topCard.TranslationX = (xDiff); - - // Calculate a angle for the card - float rotationAngel = (float)(0.3f * Math.Min(xDiff / this.Width, 1.0f)); - topCard.Rotation = rotationAngel * 57.2957795f; - - // Keep a record of how far it is moved - cardDistance = xDiff; - } - - // Scale the backcard - backCard.Scale = Math.Min(defaultSubcardScale + Math.Abs((cardDistance / CardMoveDistance) * (1.0f - defaultSubcardScale)), 1.0f); - } - - async Task HandleTouchCompleted() - { - if (itemIndex >= ItemsSource.Count) - return; - - var topCard = CardStack.Children[numberOfCards - 1]; - var backCard = CardStack.Children[numberOfCards - 2]; - - // Check if card has been dragged far enough to trigger action - if (Math.Abs(cardDistance) >= CardMoveDistance) - { - // Move card off the screen - await topCard.TranslateTo(cardDistance > 0 ? this.Width * 2 : -this.Width * 2, 0, defaultAnimationLength, Easing.SinIn); - topCard.IsVisible = false; - - // Fire events - if (cardDistance > 0) - { - Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[itemIndex], SwipeDirection.Right)); - if (SwipedRightCommand != null && SwipedRightCommand.CanExecute(ItemsSource[itemIndex])) - SwipedRightCommand.Execute(ItemsSource[itemIndex]); - } - else - { - Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[itemIndex], SwipeDirection.Left)); - if (SwipedLeftCommand != null && SwipedLeftCommand.CanExecute(ItemsSource[itemIndex])) - SwipedLeftCommand.Execute(ItemsSource[itemIndex]); - } - - // Next card - itemIndex++; - ShowNextCard(); - } - else - { - // Run animations simultaniously - await Task.WhenAll( - // Move card back to the center - topCard.TranslateTo((-topCard.X), -topCard.Y, defaultAnimationLength, Easing.SpringOut), - topCard.RotateTo(0, defaultAnimationLength, Easing.SpringOut), - - // Scale the back card down - backCard.ScaleTo(defaultSubcardScale, defaultAnimationLength, Easing.SpringOut) - ); - } - - if (itemIndex < ItemsSource.Count) - FinishedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[itemIndex])); - } - - #endregion - - void ShowNextCard() - { - if (ItemsSource == null || ItemsSource?.Count == 0) - return; - - var topCard = CardStack.Children[numberOfCards - 1]; - var backCard = CardStack.Children[numberOfCards - 2]; - - // Switch cards if this method has been called after a swipe and not at init - if (itemIndex != 0) - { - // Remove swiped-away card (topcard) from stack - CardStack.Children.Remove(topCard); - - // Scale swiped-away card (topcard) down and add it at the bottom of the stack - topCard.Scale = defaultSubcardScale; - CardStack.Children.Insert(0, topCard); - } - - // Update cards from top to back - // Start with the first card on top which is the last one on the CardStack - for (var i = numberOfCards - 1; i >= 0; i--) - { - var cardView = (CardView)CardStack.Children[i]; - cardView.Rotation = 0; - cardView.TranslationX = 0; - - // Check if an item for the card is available - var index = Math.Min((numberOfCards - 1), ItemsSource.Count) - i + itemIndex; - if (ItemsSource.Count > index) - { - cardView.Update(ItemsSource[index]); - cardView.IsVisible = true; - } - } - } - - public async void Swipe(SwipeDirection direction, uint animationLength = defaultAnimationLength) - { - // Check if there is something to swipe - if (itemIndex >= ItemsSource?.Count) - return; - - var topCard = CardStack.Children[numberOfCards - 1]; - var backCard = CardStack.Children[numberOfCards - 2]; - - // Fire events - Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[itemIndex], direction)); - if (direction == SwipeDirection.Left) - { - if (SwipedLeftCommand != null && SwipedLeftCommand.CanExecute(ItemsSource[itemIndex])) - SwipedLeftCommand.Execute(ItemsSource[itemIndex]); - } - else if (direction == SwipeDirection.Right) - { - if (SwipedRightCommand != null && SwipedRightCommand.CanExecute(ItemsSource[itemIndex])) - SwipedRightCommand.Execute(ItemsSource[itemIndex]); - } - - // Increase item index - // Do that before the animation runs - itemIndex++; - - // Animate card - await Task.WhenAll( - // Move card left or right - topCard.TranslateTo(direction == SwipeDirection.Right ? this.Width * 2 : -this.Width * 2, 0, animationLength, Easing.SinIn), - - // Rotate card (57.2957795f/3=17.18873385f) - topCard.RotateTo(direction == SwipeDirection.Right ? 17.18873385f : -17.18873385f, animationLength, Easing.SinIn), - - // Scale back card up - backCard.ScaleTo(1.0f, animationLength) - ); - topCard.IsVisible = false; - - // Next card - ShowNextCard(); - } - } + +namespace SwipeCards +{ + public partial class CardStackView : ContentView + { + private static NotifyCollectionChangedEventHandler CollectionChangedEventHandler; + + private static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if (newValue is INotifyCollectionChanged) + { + if (CollectionChangedEventHandler != null) + ((INotifyCollectionChanged)newValue).CollectionChanged -= CollectionChangedEventHandler; + + CollectionChangedEventHandler = (sender, e) => ItemsSource_CollectionChanged(sender, e, (CardStackView)bindable); + + ((INotifyCollectionChanged)newValue).CollectionChanged += CollectionChangedEventHandler; + } + + ((CardStackView)bindable).Setup(); + } + + private static void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e, CardStackView cardStackView) + { + cardStackView.Setup(); + } + + public IList ItemsSource + { + get { return (IList)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + + public static BindableProperty SwipedRightCommandProperty = BindableProperty.Create(nameof(SwipedRightCommand), typeof(ICommand), typeof(CardStackView), null); + public static BindableProperty SwipedLeftCommandProperty = BindableProperty.Create(nameof(SwipedLeftCommand), typeof(ICommand), typeof(CardStackView), null); + public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(CardStackView), null, BindingMode.TwoWay, propertyChanged: OnItemsSourcePropertyChanged); + public static readonly BindableProperty CardMoveDistanceProperty = BindableProperty.Create(nameof(CardMoveDistance), typeof(int), typeof(CardStackView), -1); + //public static readonly BindableProperty DraggableAreaMarginProperty = BindableProperty.Create(nameof(DraggableAreaMargin), typeof(Thickness), typeof(CardStackView), new Thickness(0), propertyChanged: (BindableObject bindable, object oldValue, object newValue) => { ((CardStackView)bindable).TouchObserver.Margin = (Thickness)newValue; }); + public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(CardStackView), + new DataTemplate(() => + { + var label = new Label { VerticalOptions = LayoutOptions.Center, HorizontalOptions = LayoutOptions.Center }; + label.SetBinding(Label.TextProperty, "Binding"); + return new ViewCell { View = label }; + }), + propertyChanged: OnItemTemplatePropertyChanged); + + private static void OnItemTemplatePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + ((CardStackView)bindable).Setup(); + } + + public DataTemplate ItemTemplate + { + get { return (DataTemplate)GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + + public int CardMoveDistance + { + get { return (int)GetValue(CardMoveDistanceProperty); } + set { SetValue(CardMoveDistanceProperty, value); } + } + + //public Thickness DraggableAreaMargin + //{ + // get { return (Thickness)GetValue(DraggableAreaMarginProperty); } + // set { SetValue(DraggableAreaMarginProperty, value); } + //} + + public ICommand SwipedRightCommand + { + get { return (ICommand)GetValue(SwipedRightCommandProperty); } + set { SetValue(SwipedRightCommandProperty, value); } + } + + public ICommand SwipedLeftCommand + { + get { return (ICommand)GetValue(SwipedLeftCommandProperty); } + set { SetValue(SwipedLeftCommandProperty, value); } + } + + public event EventHandler Swiped; + public event EventHandler StartedDragging; + public event EventHandler Dragging; + public event EventHandler FinishedDragging; + + private const int NumberOfCards = 2; + private const int DefaultAnimationLength = 250; + private float _defaultSubcardScale = 0.8f; + private float _cardDistance; + private int _itemIndex; + + public CardStackView() + { + InitializeComponent(); + + var panGesture = new PanGestureRecognizer(); + panGesture.PanUpdated += OnPanUpdated; + + CardStack.GestureRecognizers.Add(panGesture); + + Setup(); + } + + public void Setup() + { + // TODO: Reduce Setup() calls + // When starting the app, Setup() gets called multiple times (OnItemsSourcePropertyChanged, OnItemTemplatePropertyChanged, ...). Try to reduce that to 1 + + CardStack.Children.Clear(); + + // Add two cards (one for the front, one for the background) to the stack. Use inverse direction to ensure that first card is on top + for (var i = NumberOfCards - 1; i >= 0; i--) + { + var cardView = new CardView(ItemTemplate) + { + IsVisible = false, + Scale = (i == 0) ? 1.0f : _defaultSubcardScale //,IsEnabled = false + }; + + CardStack.Children.Add(cardView, Constraint.Constant(0), Constraint.Constant(0), Constraint.RelativeToParent((parent) => { return parent.Width; }), + Constraint.RelativeToParent((parent) => { return parent.Height; }) + ); + } + + _itemIndex = 0; + + ShowNextCard(); + } + + protected override void OnSizeAllocated(double width, double height) + { + base.OnSizeAllocated(width, height); + + // Recalculate move distance. When not set differently, this distance is 1/3 of the control's width + if (CardMoveDistance == -1 && !width.Equals(-1)) + CardMoveDistance = (int)(width / 3); + } + + private async void OnPanUpdated(object sender, PanUpdatedEventArgs e) + { + switch (e.StatusType) + { + case GestureStatus.Started: + HandleTouchStart(); + break; + case GestureStatus.Running: + HandleTouchRunning((float)e.TotalX); + break; + case GestureStatus.Completed: + await HandleTouchCompleted(); + break; + case GestureStatus.Canceled: + break; + } + } + + private void HandleTouchStart() + { + if (_itemIndex < ItemsSource.Count) + StartedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], 0)); + } + + private void HandleTouchRunning(float xDiff) + { + if (_itemIndex >= ItemsSource.Count) + return; + + var topCard = CardStack.Children[NumberOfCards - 1]; + var backCard = CardStack.Children[NumberOfCards - 2]; + + // Move the top card + if (topCard.IsVisible) + { + // Move the card + topCard.TranslationX = (xDiff); + + // Calculate a angle for the card + float rotationAngel = (float)(0.3f * Math.Min(xDiff / this.Width, 1.0f)); + topCard.Rotation = rotationAngel * 57.2957795f; + + // Keep a record of how far it is moved + _cardDistance = xDiff; + + Dragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], _cardDistance)); + } + + // Scale the backcard + backCard.Scale = Math.Min(_defaultSubcardScale + Math.Abs((_cardDistance / CardMoveDistance) * (1.0f - _defaultSubcardScale)), 1.0f); + } + + private async Task HandleTouchCompleted() + { + if (_itemIndex >= ItemsSource.Count) + return; + + var topCard = CardStack.Children[NumberOfCards - 1]; + var backCard = CardStack.Children[NumberOfCards - 2]; + + // Check if card has been dragged far enough to trigger action + if (Math.Abs(_cardDistance) >= CardMoveDistance) + { + // Move card off the screen + await topCard.TranslateTo(_cardDistance > 0 ? this.Width * 2 : -this.Width * 2, 0, DefaultAnimationLength, Easing.SinIn); + topCard.IsVisible = false; + + // Fire events + if (_cardDistance > 0) + { + Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[_itemIndex], SwipeDirection.Right)); + + if (SwipedRightCommand != null && SwipedRightCommand.CanExecute(ItemsSource[_itemIndex])) + SwipedRightCommand.Execute(ItemsSource[_itemIndex]); + } + else + { + Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[_itemIndex], SwipeDirection.Left)); + + if (SwipedLeftCommand != null && SwipedLeftCommand.CanExecute(ItemsSource[_itemIndex])) + SwipedLeftCommand.Execute(ItemsSource[_itemIndex]); + } + + _itemIndex++; + + ShowNextCard(); + } + else + { + // Run animations simultaniously + await Task.WhenAll( + + topCard.TranslateTo((-topCard.X), -topCard.Y, DefaultAnimationLength, Easing.SpringOut), // Move card back to the center + topCard.RotateTo(0, DefaultAnimationLength, Easing.SpringOut), + backCard.ScaleTo(_defaultSubcardScale, DefaultAnimationLength, Easing.SpringOut) // Scale the back card down + ); + } + + if (_itemIndex < ItemsSource.Count) + FinishedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], _cardDistance)); + } + + private void ShowNextCard() + { + if (ItemsSource == null || ItemsSource?.Count == 0) + return; + + var topCard = CardStack.Children[NumberOfCards - 1]; + var backCard = CardStack.Children[NumberOfCards - 2]; + + // Switch cards if this method has been called after a swipe and not at init + if (_itemIndex != 0) + { + CardStack.Children.Remove(topCard); + + // Scale swiped-away card (topcard) down and add it at the bottom of the stack + topCard.Scale = _defaultSubcardScale; + CardStack.Children.Insert(0, topCard); + } + + // Update cards from top to back // Start with the first card on top which is the last one on the CardStack + for (var i = NumberOfCards - 1; i >= 0; i--) + { + var cardView = (CardView)CardStack.Children[i]; + + cardView.Rotation = 0; + cardView.TranslationX = 0; + + // Check if an item for the card is available + var index = Math.Min((NumberOfCards - 1), ItemsSource.Count) - i + _itemIndex; + + if (ItemsSource.Count > index) + { + cardView.Update(ItemsSource[index]); + cardView.IsVisible = true; + } + } + } + + public async Task Swipe(SwipeDirection direction, uint animationLength = DefaultAnimationLength) + { + if (_itemIndex >= ItemsSource?.Count) + return; + + var topCard = CardStack.Children[NumberOfCards - 1]; + var backCard = CardStack.Children[NumberOfCards - 2]; + + Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[_itemIndex], direction)); + + if (direction == SwipeDirection.Left) + { + if (SwipedLeftCommand != null && SwipedLeftCommand.CanExecute(ItemsSource[_itemIndex])) + SwipedLeftCommand.Execute(ItemsSource[_itemIndex]); + } + else if (direction == SwipeDirection.Right) + { + if (SwipedRightCommand != null && SwipedRightCommand.CanExecute(ItemsSource[_itemIndex])) + SwipedRightCommand.Execute(ItemsSource[_itemIndex]); + } + + // Increase item index // Do that before the animation runs + _itemIndex++; + + await Task.WhenAll( + + topCard.TranslateTo(direction == SwipeDirection.Right ? this.Width * 2 : -this.Width * 2, 0, animationLength, Easing.SinIn), + topCard.RotateTo(direction == SwipeDirection.Right ? 17.18873385f : -17.18873385f, animationLength, Easing.SinIn), // Rotate card (57.2957795f/3=17.18873385f) + backCard.ScaleTo(1.0f, animationLength) // Scale back card up + ); + + topCard.IsVisible = false; + + ShowNextCard(); + } + } } diff --git a/SwipeCards.Controls/Views/CardView.xaml b/SwipeCards.Controls/Views/CardView.xaml index 50f2228..30d1971 100644 --- a/SwipeCards.Controls/Views/CardView.xaml +++ b/SwipeCards.Controls/Views/CardView.xaml @@ -1,11 +1,13 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> - + + + + diff --git a/SwipeCards.Controls/Views/CardView.xaml.cs b/SwipeCards.Controls/Views/CardView.xaml.cs index 9dc2027..ac6b13c 100644 --- a/SwipeCards.Controls/Views/CardView.xaml.cs +++ b/SwipeCards.Controls/Views/CardView.xaml.cs @@ -1,24 +1,19 @@ -using System; -using System.Collections.Generic; -using Xamarin.Forms; -using System.ComponentModel; -using System.Runtime.CompilerServices; +using Xamarin.Forms; -namespace SwipeCards.Controls +namespace SwipeCards { - public partial class CardView : ContentView - { - public CardView(DataTemplate itemTemplate) - { - InitializeComponent(); - Container.Content = itemTemplate.CreateContent() as View; - } + public partial class CardView : ContentView + { + public CardView(DataTemplate itemTemplate) + { + InitializeComponent(); - public void Update(object item) - { - //Container.IsVisible = false; - Container.Content.BindingContext = item; - //Container.IsVisible = true; - } - } + Container.Content = itemTemplate.CreateContent() as View; + } + + public void Update(object item) + { + Container.Content.BindingContext = item; + } + } } From 7d997e070894bc375f1978784c2bff25048bfb39 Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Tue, 8 May 2018 12:28:57 +0200 Subject: [PATCH 03/14] updated xf version fixed heigth overflow fixed setup useless calls --- .../SwipeCards.Controls.csproj | 2 +- SwipeCards.Controls/Views/CardStackView.xaml | 2 +- .../Views/CardStackView.xaml.cs | 138 ++++++++---------- SwipeCards.Controls/Views/CardView.xaml | 13 +- 4 files changed, 64 insertions(+), 91 deletions(-) diff --git a/SwipeCards.Controls/SwipeCards.Controls.csproj b/SwipeCards.Controls/SwipeCards.Controls.csproj index c02e25b..b4d4922 100644 --- a/SwipeCards.Controls/SwipeCards.Controls.csproj +++ b/SwipeCards.Controls/SwipeCards.Controls.csproj @@ -34,8 +34,8 @@ - + \ No newline at end of file diff --git a/SwipeCards.Controls/Views/CardStackView.xaml b/SwipeCards.Controls/Views/CardStackView.xaml index b85d0e5..be477dc 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml +++ b/SwipeCards.Controls/Views/CardStackView.xaml @@ -1,6 +1,6 @@ - + diff --git a/SwipeCards.Controls/Views/CardStackView.xaml.cs b/SwipeCards.Controls/Views/CardStackView.xaml.cs index 0ea9289..b8e9090 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml.cs +++ b/SwipeCards.Controls/Views/CardStackView.xaml.cs @@ -6,54 +6,33 @@ using System.Collections.Specialized; namespace SwipeCards -{ +{ + public enum SwipeMode + { + Tinder, + Carousel + } + public partial class CardStackView : ContentView { - private static NotifyCollectionChangedEventHandler CollectionChangedEventHandler; - - private static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue) - { - if (newValue is INotifyCollectionChanged) - { - if (CollectionChangedEventHandler != null) - ((INotifyCollectionChanged)newValue).CollectionChanged -= CollectionChangedEventHandler; - - CollectionChangedEventHandler = (sender, e) => ItemsSource_CollectionChanged(sender, e, (CardStackView)bindable); + private static NotifyCollectionChangedEventHandler CollectionChangedEventHandler; - ((INotifyCollectionChanged)newValue).CollectionChanged += CollectionChangedEventHandler; - } - - ((CardStackView)bindable).Setup(); - } - - private static void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e, CardStackView cardStackView) - { - cardStackView.Setup(); - } - - public IList ItemsSource - { - get { return (IList)GetValue(ItemsSourceProperty); } - set { SetValue(ItemsSourceProperty, value); } - } + private const int NumberOfCards = 2; + private const int DefaultAnimationLength = 250; + private float _defaultSubcardScale = 0.8f; + private float _cardDistance; + private int _itemIndex; public static BindableProperty SwipedRightCommandProperty = BindableProperty.Create(nameof(SwipedRightCommand), typeof(ICommand), typeof(CardStackView), null); public static BindableProperty SwipedLeftCommandProperty = BindableProperty.Create(nameof(SwipedLeftCommand), typeof(ICommand), typeof(CardStackView), null); public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(CardStackView), null, BindingMode.TwoWay, propertyChanged: OnItemsSourcePropertyChanged); public static readonly BindableProperty CardMoveDistanceProperty = BindableProperty.Create(nameof(CardMoveDistance), typeof(int), typeof(CardStackView), -1); - //public static readonly BindableProperty DraggableAreaMarginProperty = BindableProperty.Create(nameof(DraggableAreaMargin), typeof(Thickness), typeof(CardStackView), new Thickness(0), propertyChanged: (BindableObject bindable, object oldValue, object newValue) => { ((CardStackView)bindable).TouchObserver.Margin = (Thickness)newValue; }); - public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(CardStackView), - new DataTemplate(() => - { - var label = new Label { VerticalOptions = LayoutOptions.Center, HorizontalOptions = LayoutOptions.Center }; - label.SetBinding(Label.TextProperty, "Binding"); - return new ViewCell { View = label }; - }), - propertyChanged: OnItemTemplatePropertyChanged); - - private static void OnItemTemplatePropertyChanged(BindableObject bindable, object oldValue, object newValue) + public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(CardStackView)); + + public IList ItemsSource { - ((CardStackView)bindable).Setup(); + get { return (IList)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } } public DataTemplate ItemTemplate @@ -68,12 +47,6 @@ public int CardMoveDistance set { SetValue(CardMoveDistanceProperty, value); } } - //public Thickness DraggableAreaMargin - //{ - // get { return (Thickness)GetValue(DraggableAreaMarginProperty); } - // set { SetValue(DraggableAreaMarginProperty, value); } - //} - public ICommand SwipedRightCommand { get { return (ICommand)GetValue(SwipedRightCommandProperty); } @@ -86,17 +59,13 @@ public ICommand SwipedLeftCommand set { SetValue(SwipedLeftCommandProperty, value); } } + public SwipeMode SwipeMode { get; set; } = SwipeMode.Tinder; + public event EventHandler Swiped; public event EventHandler StartedDragging; public event EventHandler Dragging; public event EventHandler FinishedDragging; - private const int NumberOfCards = 2; - private const int DefaultAnimationLength = 250; - private float _defaultSubcardScale = 0.8f; - private float _cardDistance; - private int _itemIndex; - public CardStackView() { InitializeComponent(); @@ -105,24 +74,21 @@ public CardStackView() panGesture.PanUpdated += OnPanUpdated; CardStack.GestureRecognizers.Add(panGesture); - - Setup(); } public void Setup() { - // TODO: Reduce Setup() calls - // When starting the app, Setup() gets called multiple times (OnItemsSourcePropertyChanged, OnItemTemplatePropertyChanged, ...). Try to reduce that to 1 - CardStack.Children.Clear(); - // Add two cards (one for the front, one for the background) to the stack. Use inverse direction to ensure that first card is on top + if (ItemsSource != null && ItemsSource.Count == 0) + return; + for (var i = NumberOfCards - 1; i >= 0; i--) { var cardView = new CardView(ItemTemplate) { IsVisible = false, - Scale = (i == 0) ? 1.0f : _defaultSubcardScale //,IsEnabled = false + Scale = (i == 0) ? 1.0f : _defaultSubcardScale }; CardStack.Children.Add(cardView, Constraint.Constant(0), Constraint.Constant(0), Constraint.RelativeToParent((parent) => { return parent.Width; }), @@ -134,6 +100,26 @@ public void Setup() ShowNextCard(); } + + private static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if (newValue is INotifyCollectionChanged) + { + if (CollectionChangedEventHandler != null) + ((INotifyCollectionChanged)newValue).CollectionChanged -= CollectionChangedEventHandler; + + CollectionChangedEventHandler = (sender, e) => ItemsSource_CollectionChanged(sender, e, (CardStackView)bindable); + + ((INotifyCollectionChanged)newValue).CollectionChanged += CollectionChangedEventHandler; + } + + ((CardStackView)bindable).Setup(); + } + + private static void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e, CardStackView cardStackView) + { + cardStackView.Setup(); + } protected override void OnSizeAllocated(double width, double height) { @@ -168,7 +154,7 @@ private void HandleTouchStart() StartedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], 0)); } - private void HandleTouchRunning(float xDiff) + private void HandleTouchRunning(float horizontalTraslation) { if (_itemIndex >= ItemsSource.Count) return; @@ -176,23 +162,21 @@ private void HandleTouchRunning(float xDiff) var topCard = CardStack.Children[NumberOfCards - 1]; var backCard = CardStack.Children[NumberOfCards - 2]; - // Move the top card if (topCard.IsVisible) { - // Move the card - topCard.TranslationX = (xDiff); + topCard.TranslationX = (horizontalTraslation); - // Calculate a angle for the card - float rotationAngel = (float)(0.3f * Math.Min(xDiff / this.Width, 1.0f)); - topCard.Rotation = rotationAngel * 57.2957795f; + if (SwipeMode == SwipeMode.Tinder) + { + var rotationAngel = (float)(0.3f * Math.Min(horizontalTraslation / Width, 1.0f)); + topCard.Rotation = rotationAngel * 57.2957795f; + } - // Keep a record of how far it is moved - _cardDistance = xDiff; + _cardDistance = horizontalTraslation; Dragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], _cardDistance)); } - // Scale the backcard backCard.Scale = Math.Min(_defaultSubcardScale + Math.Abs((_cardDistance / CardMoveDistance) * (1.0f - _defaultSubcardScale)), 1.0f); } @@ -204,14 +188,11 @@ private async Task HandleTouchCompleted() var topCard = CardStack.Children[NumberOfCards - 1]; var backCard = CardStack.Children[NumberOfCards - 2]; - // Check if card has been dragged far enough to trigger action if (Math.Abs(_cardDistance) >= CardMoveDistance) { - // Move card off the screen - await topCard.TranslateTo(_cardDistance > 0 ? this.Width * 2 : -this.Width * 2, 0, DefaultAnimationLength, Easing.SinIn); + await topCard.TranslateTo(_cardDistance > 0 ? Width * 2 : -Width * 2, 0, DefaultAnimationLength, Easing.SinIn); topCard.IsVisible = false; - // Fire events if (_cardDistance > 0) { Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[_itemIndex], SwipeDirection.Right)); @@ -233,12 +214,11 @@ private async Task HandleTouchCompleted() } else { - // Run animations simultaniously await Task.WhenAll( - topCard.TranslateTo((-topCard.X), -topCard.Y, DefaultAnimationLength, Easing.SpringOut), // Move card back to the center - topCard.RotateTo(0, DefaultAnimationLength, Easing.SpringOut), - backCard.ScaleTo(_defaultSubcardScale, DefaultAnimationLength, Easing.SpringOut) // Scale the back card down + topCard.TranslateTo((-topCard.X), -topCard.Y, DefaultAnimationLength, Easing.SpringOut), + topCard.RotateTo(0, DefaultAnimationLength, Easing.SpringOut), + backCard.ScaleTo(_defaultSubcardScale, DefaultAnimationLength, Easing.SpringOut) ); } @@ -309,9 +289,9 @@ public async Task Swipe(SwipeDirection direction, uint animationLength = Default await Task.WhenAll( - topCard.TranslateTo(direction == SwipeDirection.Right ? this.Width * 2 : -this.Width * 2, 0, animationLength, Easing.SinIn), - topCard.RotateTo(direction == SwipeDirection.Right ? 17.18873385f : -17.18873385f, animationLength, Easing.SinIn), // Rotate card (57.2957795f/3=17.18873385f) - backCard.ScaleTo(1.0f, animationLength) // Scale back card up + topCard.TranslateTo(direction == SwipeDirection.Right ? Width * 2 : -Width * 2, 0, animationLength, Easing.SinIn), + topCard.RotateTo(direction == SwipeDirection.Right ? 17.18873385f : -17.18873385f, animationLength, Easing.SinIn), + backCard.ScaleTo(1.0f, animationLength) ); topCard.IsVisible = false; diff --git a/SwipeCards.Controls/Views/CardView.xaml b/SwipeCards.Controls/Views/CardView.xaml index 30d1971..f0a8e87 100644 --- a/SwipeCards.Controls/Views/CardView.xaml +++ b/SwipeCards.Controls/Views/CardView.xaml @@ -1,13 +1,6 @@ - + + - - - - - - + From 5676f012a4b32115ea594c1c9b2392c31b781af2 Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Mon, 14 May 2018 12:17:27 +0200 Subject: [PATCH 04/14] update xf --- SwipeCards.Controls/SwipeCards.Controls.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SwipeCards.Controls/SwipeCards.Controls.csproj b/SwipeCards.Controls/SwipeCards.Controls.csproj index b4d4922..0bc5b1e 100644 --- a/SwipeCards.Controls/SwipeCards.Controls.csproj +++ b/SwipeCards.Controls/SwipeCards.Controls.csproj @@ -35,7 +35,7 @@ - + \ No newline at end of file From ac0a54f10343726e664315f5def9b4fe2e614cbd Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Tue, 15 May 2018 11:24:48 +0200 Subject: [PATCH 05/14] aggiornato xxf --- SwipeCards.Controls/SwipeCards.Controls.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SwipeCards.Controls/SwipeCards.Controls.csproj b/SwipeCards.Controls/SwipeCards.Controls.csproj index 0bc5b1e..00a85ee 100644 --- a/SwipeCards.Controls/SwipeCards.Controls.csproj +++ b/SwipeCards.Controls/SwipeCards.Controls.csproj @@ -34,8 +34,8 @@ - - + + \ No newline at end of file From a6dd2a17c473063bd78185a2d83cac35e502c2ed Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Wed, 16 May 2018 17:38:57 +0200 Subject: [PATCH 06/14] added NoMoreCards event --- SwipeCards.Controls/Views/CardStackView.xaml.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/SwipeCards.Controls/Views/CardStackView.xaml.cs b/SwipeCards.Controls/Views/CardStackView.xaml.cs index b8e9090..27d18d6 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml.cs +++ b/SwipeCards.Controls/Views/CardStackView.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Xamarin.Forms; using System.Collections; using System.Threading.Tasks; @@ -64,7 +65,8 @@ public ICommand SwipedLeftCommand public event EventHandler Swiped; public event EventHandler StartedDragging; public event EventHandler Dragging; - public event EventHandler FinishedDragging; + public event EventHandler FinishedDragging; + public event EventHandler NoMoreCards; public CardStackView() { @@ -265,8 +267,11 @@ private void ShowNextCard() public async Task Swipe(SwipeDirection direction, uint animationLength = DefaultAnimationLength) { - if (_itemIndex >= ItemsSource?.Count) - return; + if (_itemIndex >= ItemsSource?.Count) + { + NoMoreCards?.Invoke(this, new EventArgs()); + return; + } var topCard = CardStack.Children[NumberOfCards - 1]; var backCard = CardStack.Children[NumberOfCards - 2]; From 716e541f142aa29c456dded84d0d68269fde8e2d Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Thu, 17 May 2018 12:38:47 +0200 Subject: [PATCH 07/14] traslazione laterale --- .../Views/CardStackView.xaml.cs | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/SwipeCards.Controls/Views/CardStackView.xaml.cs b/SwipeCards.Controls/Views/CardStackView.xaml.cs index 27d18d6..bb0101a 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml.cs +++ b/SwipeCards.Controls/Views/CardStackView.xaml.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using System.Windows.Input; using System.Collections.Specialized; +using System.Diagnostics; namespace SwipeCards { @@ -20,7 +21,9 @@ public partial class CardStackView : ContentView private const int NumberOfCards = 2; private const int DefaultAnimationLength = 250; - private float _defaultSubcardScale = 0.8f; + private float _defaultSubcardScale = 0.9f; + private float _defaultSubcardTranslationX = -30; + private float _defaultSubcardOpacity = .6f; private float _cardDistance; private int _itemIndex; @@ -90,7 +93,9 @@ public void Setup() var cardView = new CardView(ItemTemplate) { IsVisible = false, - Scale = (i == 0) ? 1.0f : _defaultSubcardScale + Scale = (i == 0) ? 1 : _defaultSubcardScale, + TranslationX = (i == 0) ? 0 : _defaultSubcardTranslationX, + Opacity = (i == 0) ? 0 : _defaultSubcardOpacity }; CardStack.Children.Add(cardView, Constraint.Constant(0), Constraint.Constant(0), Constraint.RelativeToParent((parent) => { return parent.Width; }), @@ -166,12 +171,12 @@ private void HandleTouchRunning(float horizontalTraslation) if (topCard.IsVisible) { - topCard.TranslationX = (horizontalTraslation); + topCard.TranslationX = horizontalTraslation; if (SwipeMode == SwipeMode.Tinder) { - var rotationAngel = (float)(0.3f * Math.Min(horizontalTraslation / Width, 1.0f)); - topCard.Rotation = rotationAngel * 57.2957795f; + var rotationAngle = (float)(0.3f * Math.Min(horizontalTraslation / Width, 1.0f)); + topCard.Rotation = rotationAngle * 57.2957795f; } _cardDistance = horizontalTraslation; @@ -179,7 +184,9 @@ private void HandleTouchRunning(float horizontalTraslation) Dragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], _cardDistance)); } - backCard.Scale = Math.Min(_defaultSubcardScale + Math.Abs((_cardDistance / CardMoveDistance) * (1.0f - _defaultSubcardScale)), 1.0f); + backCard.Scale = Math.Min(_defaultSubcardScale + Math.Abs((_cardDistance / CardMoveDistance) * (1 - _defaultSubcardScale)), 1); + backCard.TranslationX = Math.Min(_defaultSubcardTranslationX + Math.Abs((_cardDistance / CardMoveDistance) * _defaultSubcardTranslationX), 0); + backCard.Opacity = Math.Min(_defaultSubcardOpacity + Math.Abs((_cardDistance / CardMoveDistance) * _defaultSubcardOpacity), 1); } private async Task HandleTouchCompleted() @@ -192,7 +199,8 @@ private async Task HandleTouchCompleted() if (Math.Abs(_cardDistance) >= CardMoveDistance) { - await topCard.TranslateTo(_cardDistance > 0 ? Width * 2 : -Width * 2, 0, DefaultAnimationLength, Easing.SinIn); + await topCard.TranslateTo(_cardDistance > 0 ? Width * 2 : -Width * 2, 0, DefaultAnimationLength, Easing.SinIn); + topCard.IsVisible = false; if (_cardDistance > 0) @@ -220,7 +228,9 @@ await Task.WhenAll( topCard.TranslateTo((-topCard.X), -topCard.Y, DefaultAnimationLength, Easing.SpringOut), topCard.RotateTo(0, DefaultAnimationLength, Easing.SpringOut), - backCard.ScaleTo(_defaultSubcardScale, DefaultAnimationLength, Easing.SpringOut) + backCard.ScaleTo(_defaultSubcardScale, DefaultAnimationLength, Easing.SpringOut), + backCard.TranslateTo(_defaultSubcardTranslationX, 0, DefaultAnimationLength, Easing.SpringOut), + backCard.FadeTo(_defaultSubcardOpacity, DefaultAnimationLength, Easing.SpringOut) ); } @@ -234,7 +244,7 @@ private void ShowNextCard() return; var topCard = CardStack.Children[NumberOfCards - 1]; - var backCard = CardStack.Children[NumberOfCards - 2]; + //var backCard = CardStack.Children[NumberOfCards - 2]; // Switch cards if this method has been called after a swipe and not at init if (_itemIndex != 0) @@ -246,21 +256,28 @@ private void ShowNextCard() CardStack.Children.Insert(0, topCard); } - // Update cards from top to back // Start with the first card on top which is the last one on the CardStack + // Update cards from top to back. Start with the first card on top which is the last one on the CardStack for (var i = NumberOfCards - 1; i >= 0; i--) { var cardView = (CardView)CardStack.Children[i]; cardView.Rotation = 0; - cardView.TranslationX = 0; + cardView.TranslationX = _defaultSubcardTranslationX * (NumberOfCards - 1 - i); + cardView.Opacity = i == NumberOfCards - 1 ? 1 : _defaultSubcardOpacity; // Check if an item for the card is available var index = Math.Min((NumberOfCards - 1), ItemsSource.Count) - i + _itemIndex; if (ItemsSource.Count > index) { - cardView.Update(ItemsSource[index]); - cardView.IsVisible = true; + cardView.Update(ItemsSource[index]); + + if (!cardView.IsVisible) + { + cardView.IsVisible = true; + } + + //await cardView.TranslateTo(-_defaultSubcardTranslationX * i, 0, DefaultAnimationLength, Easing.SpringOut); } } } @@ -296,9 +313,14 @@ await Task.WhenAll( topCard.TranslateTo(direction == SwipeDirection.Right ? Width * 2 : -Width * 2, 0, animationLength, Easing.SinIn), topCard.RotateTo(direction == SwipeDirection.Right ? 17.18873385f : -17.18873385f, animationLength, Easing.SinIn), - backCard.ScaleTo(1.0f, animationLength) + backCard.ScaleTo(1.0f, animationLength), + backCard.TranslateTo(0, 0, animationLength, Easing.SinIn), + backCard.FadeTo(1, animationLength, Easing.SinIn) ); + //var visual = new VisualElement(); + //visual.FadeTo(1, animationLength, Easing.SinIn) + topCard.IsVisible = false; ShowNextCard(); From e2c936be18d5368a116f07630668bd7c4165b7f0 Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Thu, 17 May 2018 14:32:11 +0200 Subject: [PATCH 08/14] fix --- SwipeCards.Controls/Views/CardStackView.xaml.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SwipeCards.Controls/Views/CardStackView.xaml.cs b/SwipeCards.Controls/Views/CardStackView.xaml.cs index bb0101a..0c1905e 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml.cs +++ b/SwipeCards.Controls/Views/CardStackView.xaml.cs @@ -274,10 +274,11 @@ private void ShowNextCard() if (!cardView.IsVisible) { - cardView.IsVisible = true; + cardView.TranslationX = -cardView.Width + _defaultSubcardTranslationX; + cardView.IsVisible = true; + + cardView.TranslateTo(_defaultSubcardTranslationX * (NumberOfCards - 1 - i), 0, DefaultAnimationLength, Easing.Linear); } - - //await cardView.TranslateTo(-_defaultSubcardTranslationX * i, 0, DefaultAnimationLength, Easing.SpringOut); } } } From 19646f4292843eb5dd3e81902967d3ef536cdf0b Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Mon, 21 May 2018 15:41:04 +0200 Subject: [PATCH 09/14] giga-workaround for the android bug that mixes tap and pan gestures and the "i-will-not-fire-oncomplete-event-on-pan" bug --- .../SwipeCards.Controls.csproj | 1 + .../Views/CardStackView.xaml.cs | 72 +++++++++++++++---- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/SwipeCards.Controls/SwipeCards.Controls.csproj b/SwipeCards.Controls/SwipeCards.Controls.csproj index 00a85ee..475af2e 100644 --- a/SwipeCards.Controls/SwipeCards.Controls.csproj +++ b/SwipeCards.Controls/SwipeCards.Controls.csproj @@ -13,6 +13,7 @@ SwipeCards for Xamarin.Forms true Add methods for programmatic swiping. + netstandard1.6 diff --git a/SwipeCards.Controls/Views/CardStackView.xaml.cs b/SwipeCards.Controls/Views/CardStackView.xaml.cs index 0c1905e..b107130 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml.cs +++ b/SwipeCards.Controls/Views/CardStackView.xaml.cs @@ -74,11 +74,18 @@ public ICommand SwipedLeftCommand public CardStackView() { InitializeComponent(); - + var panGesture = new PanGestureRecognizer(); panGesture.PanUpdated += OnPanUpdated; - CardStack.GestureRecognizers.Add(panGesture); + CardStack.GestureRecognizers.Add(panGesture); + + MessagingCenter.Subscribe(this, "UP", async (arg) => + { + Debug.WriteLine($"FakeCompleted: {_cardDistance}"); + + await HandleTouchCompleted(); + }); } public void Setup() @@ -142,30 +149,63 @@ private async void OnPanUpdated(object sender, PanUpdatedEventArgs e) switch (e.StatusType) { case GestureStatus.Started: + HandleTouchStart(); - break; + + Debug.WriteLine($"Started: {_cardDistance}, x:{e.TotalX} y:{e.TotalY}"); + break; + case GestureStatus.Running: + HandleTouchRunning((float)e.TotalX); - break; + + Debug.WriteLine($"Running: {_cardDistance}, x:{e.TotalX} y:{e.TotalY}, -> _lastX: {_lastX}"); + break; + case GestureStatus.Completed: + await HandleTouchCompleted(); - break; + + Debug.WriteLine($"Completed: {_cardDistance}, x:{e.TotalX} y:{e.TotalY}"); + break; + case GestureStatus.Canceled: break; } - } + } + + private bool _isDragging; + private double _lastX; + private const double DeltaX = 100; private void HandleTouchStart() { - if (_itemIndex < ItemsSource.Count) - StartedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], 0)); + if (_itemIndex >= ItemsSource.Count) + return; + + if (_cardDistance != 0) + return; + + _lastX = 0; + + _isDragging = true; + + StartedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], 0)); } private void HandleTouchRunning(float horizontalTraslation) { if (_itemIndex >= ItemsSource.Count) - return; - + return; + + if (!_isDragging) + return; + + if (horizontalTraslation - _lastX > DeltaX) + return; + + _lastX = horizontalTraslation; + var topCard = CardStack.Children[NumberOfCards - 1]; var backCard = CardStack.Children[NumberOfCards - 2]; @@ -192,7 +232,10 @@ private void HandleTouchRunning(float horizontalTraslation) private async Task HandleTouchCompleted() { if (_itemIndex >= ItemsSource.Count) - return; + return; + + _lastX = 0; + _isDragging = false; var topCard = CardStack.Children[NumberOfCards - 1]; var backCard = CardStack.Children[NumberOfCards - 2]; @@ -232,7 +275,9 @@ await Task.WhenAll( backCard.TranslateTo(_defaultSubcardTranslationX, 0, DefaultAnimationLength, Easing.SpringOut), backCard.FadeTo(_defaultSubcardOpacity, DefaultAnimationLength, Easing.SpringOut) ); - } + } + + _cardDistance = 0; if (_itemIndex < ItemsSource.Count) FinishedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], _cardDistance)); @@ -319,9 +364,6 @@ await Task.WhenAll( backCard.FadeTo(1, animationLength, Easing.SinIn) ); - //var visual = new VisualElement(); - //visual.FadeTo(1, animationLength, Easing.SinIn) - topCard.IsVisible = false; ShowNextCard(); From ffab6d176779fa6785b3f4f32cd05c21c03516b0 Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Mon, 21 May 2018 17:10:07 +0200 Subject: [PATCH 10/14] added Swipe method --- .../Views/CardStackView.xaml.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/SwipeCards.Controls/Views/CardStackView.xaml.cs b/SwipeCards.Controls/Views/CardStackView.xaml.cs index b107130..12c1db9 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml.cs +++ b/SwipeCards.Controls/Views/CardStackView.xaml.cs @@ -283,6 +283,33 @@ await Task.WhenAll( FinishedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], _cardDistance)); } + public async Task Swipe(SwipeDirection direction) + { + if (_itemIndex >= ItemsSource.Count) + return; + + _lastX = 0; + _isDragging = false; + + var topCard = CardStack.Children[NumberOfCards - 1]; + var backCard = CardStack.Children[NumberOfCards - 2]; + + await Task.WhenAll( + topCard.TranslateTo(direction == SwipeDirection.Right ? Width * 2 : -Width * 2, 0, DefaultAnimationLength, Easing.SinIn), + backCard.ScaleTo(1, DefaultAnimationLength, Easing.SpringOut), + backCard.TranslateTo(0, 0, DefaultAnimationLength, Easing.SpringOut), + backCard.FadeTo(1, DefaultAnimationLength, Easing.SpringOut) + ); + + topCard.IsVisible = false; + + _itemIndex++; + + ShowNextCard(); + + _cardDistance = 0; + } + private void ShowNextCard() { if (ItemsSource == null || ItemsSource?.Count == 0) From 2ec19e6e5956b6a9959ea10ed189ab99bc5ed36e Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Tue, 22 May 2018 12:26:22 +0200 Subject: [PATCH 11/14] bug fix --- SwipeCards.Controls/Views/CardStackView.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SwipeCards.Controls/Views/CardStackView.xaml.cs b/SwipeCards.Controls/Views/CardStackView.xaml.cs index 12c1db9..28b8e8b 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml.cs +++ b/SwipeCards.Controls/Views/CardStackView.xaml.cs @@ -231,7 +231,7 @@ private void HandleTouchRunning(float horizontalTraslation) private async Task HandleTouchCompleted() { - if (_itemIndex >= ItemsSource.Count) + if (_itemIndex >= ItemsSource.Count || !_isDragging) return; _lastX = 0; From 76f85470a4395fda9d7aaeb69a5e0ea995a8894c Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Tue, 22 May 2018 15:45:41 +0200 Subject: [PATCH 12/14] removed SwipeMode property --- .../Views/CardStackView.xaml.cs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/SwipeCards.Controls/Views/CardStackView.xaml.cs b/SwipeCards.Controls/Views/CardStackView.xaml.cs index 28b8e8b..b3063e7 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml.cs +++ b/SwipeCards.Controls/Views/CardStackView.xaml.cs @@ -8,13 +8,7 @@ using System.Diagnostics; namespace SwipeCards -{ - public enum SwipeMode - { - Tinder, - Carousel - } - +{ public partial class CardStackView : ContentView { private static NotifyCollectionChangedEventHandler CollectionChangedEventHandler; @@ -63,8 +57,6 @@ public ICommand SwipedLeftCommand set { SetValue(SwipedLeftCommandProperty, value); } } - public SwipeMode SwipeMode { get; set; } = SwipeMode.Tinder; - public event EventHandler Swiped; public event EventHandler StartedDragging; public event EventHandler Dragging; @@ -86,6 +78,20 @@ public CardStackView() await HandleTouchCompleted(); }); + + /* put this in MainActivity for workaround to work -> https://github.com/xamarin/Xamarin.Forms/issues/1495 + + public override bool DispatchTouchEvent(MotionEvent ev) + { + if (ev.Action == MotionEventActions.Up) + { + MessagingCenter.Send(this, "UP"); + } + + return base.DispatchTouchEvent(ev); + } + + */ } public void Setup() @@ -139,7 +145,6 @@ protected override void OnSizeAllocated(double width, double height) { base.OnSizeAllocated(width, height); - // Recalculate move distance. When not set differently, this distance is 1/3 of the control's width if (CardMoveDistance == -1 && !width.Equals(-1)) CardMoveDistance = (int)(width / 3); } @@ -213,11 +218,8 @@ private void HandleTouchRunning(float horizontalTraslation) { topCard.TranslationX = horizontalTraslation; - if (SwipeMode == SwipeMode.Tinder) - { - var rotationAngle = (float)(0.3f * Math.Min(horizontalTraslation / Width, 1.0f)); - topCard.Rotation = rotationAngle * 57.2957795f; - } + var rotationAngle = (float)(0.3f * Math.Min(horizontalTraslation / Width, 1.0f)); + topCard.Rotation = rotationAngle * 57.2957795f; _cardDistance = horizontalTraslation; @@ -316,19 +318,15 @@ private void ShowNextCard() return; var topCard = CardStack.Children[NumberOfCards - 1]; - //var backCard = CardStack.Children[NumberOfCards - 2]; - // Switch cards if this method has been called after a swipe and not at init if (_itemIndex != 0) { CardStack.Children.Remove(topCard); - // Scale swiped-away card (topcard) down and add it at the bottom of the stack topCard.Scale = _defaultSubcardScale; CardStack.Children.Insert(0, topCard); } - // Update cards from top to back. Start with the first card on top which is the last one on the CardStack for (var i = NumberOfCards - 1; i >= 0; i--) { var cardView = (CardView)CardStack.Children[i]; @@ -337,7 +335,6 @@ private void ShowNextCard() cardView.TranslationX = _defaultSubcardTranslationX * (NumberOfCards - 1 - i); cardView.Opacity = i == NumberOfCards - 1 ? 1 : _defaultSubcardOpacity; - // Check if an item for the card is available var index = Math.Min((NumberOfCards - 1), ItemsSource.Count) - i + _itemIndex; if (ItemsSource.Count > index) @@ -379,7 +376,6 @@ public async Task Swipe(SwipeDirection direction, uint animationLength = Default SwipedRightCommand.Execute(ItemsSource[_itemIndex]); } - // Increase item index // Do that before the animation runs _itemIndex++; await Task.WhenAll( From 7b62347db8b373da41a80cbd308361176aa1678f Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Tue, 29 May 2018 12:26:40 +0200 Subject: [PATCH 13/14] fix bug --- .../SwipeCards.Controls.csproj | 3 + .../Views/CardStackView.xaml.cs | 625 ++++++++---------- 2 files changed, 296 insertions(+), 332 deletions(-) diff --git a/SwipeCards.Controls/SwipeCards.Controls.csproj b/SwipeCards.Controls/SwipeCards.Controls.csproj index 475af2e..6414182 100644 --- a/SwipeCards.Controls/SwipeCards.Controls.csproj +++ b/SwipeCards.Controls/SwipeCards.Controls.csproj @@ -33,6 +33,9 @@ 10.0.16299.0 10.0.10240.0 + + + diff --git a/SwipeCards.Controls/Views/CardStackView.xaml.cs b/SwipeCards.Controls/Views/CardStackView.xaml.cs index b3063e7..a3d2e04 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml.cs +++ b/SwipeCards.Controls/Views/CardStackView.xaml.cs @@ -9,77 +9,77 @@ namespace SwipeCards { - public partial class CardStackView : ContentView - { - private static NotifyCollectionChangedEventHandler CollectionChangedEventHandler; - - private const int NumberOfCards = 2; - private const int DefaultAnimationLength = 250; - private float _defaultSubcardScale = 0.9f; - private float _defaultSubcardTranslationX = -30; - private float _defaultSubcardOpacity = .6f; - private float _cardDistance; - private int _itemIndex; - - public static BindableProperty SwipedRightCommandProperty = BindableProperty.Create(nameof(SwipedRightCommand), typeof(ICommand), typeof(CardStackView), null); - public static BindableProperty SwipedLeftCommandProperty = BindableProperty.Create(nameof(SwipedLeftCommand), typeof(ICommand), typeof(CardStackView), null); - public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(CardStackView), null, BindingMode.TwoWay, propertyChanged: OnItemsSourcePropertyChanged); - public static readonly BindableProperty CardMoveDistanceProperty = BindableProperty.Create(nameof(CardMoveDistance), typeof(int), typeof(CardStackView), -1); - public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(CardStackView)); - - public IList ItemsSource - { - get { return (IList)GetValue(ItemsSourceProperty); } - set { SetValue(ItemsSourceProperty, value); } - } - - public DataTemplate ItemTemplate - { - get { return (DataTemplate)GetValue(ItemTemplateProperty); } - set { SetValue(ItemTemplateProperty, value); } - } - - public int CardMoveDistance - { - get { return (int)GetValue(CardMoveDistanceProperty); } - set { SetValue(CardMoveDistanceProperty, value); } - } - - public ICommand SwipedRightCommand - { - get { return (ICommand)GetValue(SwipedRightCommandProperty); } - set { SetValue(SwipedRightCommandProperty, value); } - } - - public ICommand SwipedLeftCommand - { - get { return (ICommand)GetValue(SwipedLeftCommandProperty); } - set { SetValue(SwipedLeftCommandProperty, value); } - } - - public event EventHandler Swiped; - public event EventHandler StartedDragging; - public event EventHandler Dragging; - public event EventHandler FinishedDragging; - public event EventHandler NoMoreCards; - - public CardStackView() - { - InitializeComponent(); - - var panGesture = new PanGestureRecognizer(); - panGesture.PanUpdated += OnPanUpdated; - - CardStack.GestureRecognizers.Add(panGesture); - - MessagingCenter.Subscribe(this, "UP", async (arg) => - { - Debug.WriteLine($"FakeCompleted: {_cardDistance}"); - - await HandleTouchCompleted(); - }); - - /* put this in MainActivity for workaround to work -> https://github.com/xamarin/Xamarin.Forms/issues/1495 + public partial class CardStackView : ContentView + { + private static NotifyCollectionChangedEventHandler CollectionChangedEventHandler; + + private const int NumberOfCards = 2; + private const int DefaultAnimationLength = 250; + private float _defaultSubcardScale = 0.9f; + private float _defaultSubcardTranslationX = -30; + private float _defaultSubcardOpacity = .6f; + private float _cardDistance; + private int _itemIndex; + + public static BindableProperty SwipedRightCommandProperty = BindableProperty.Create(nameof(SwipedRightCommand), typeof(ICommand), typeof(CardStackView), null); + public static BindableProperty SwipedLeftCommandProperty = BindableProperty.Create(nameof(SwipedLeftCommand), typeof(ICommand), typeof(CardStackView), null); + public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(CardStackView), null, BindingMode.TwoWay, propertyChanged: OnItemsSourcePropertyChanged); + public static readonly BindableProperty CardMoveDistanceProperty = BindableProperty.Create(nameof(CardMoveDistance), typeof(int), typeof(CardStackView), -1); + public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(CardStackView)); + + public IList ItemsSource + { + get { return (IList)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + + public DataTemplate ItemTemplate + { + get { return (DataTemplate)GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + + public int CardMoveDistance + { + get { return (int)GetValue(CardMoveDistanceProperty); } + set { SetValue(CardMoveDistanceProperty, value); } + } + + public ICommand SwipedRightCommand + { + get { return (ICommand)GetValue(SwipedRightCommandProperty); } + set { SetValue(SwipedRightCommandProperty, value); } + } + + public ICommand SwipedLeftCommand + { + get { return (ICommand)GetValue(SwipedLeftCommandProperty); } + set { SetValue(SwipedLeftCommandProperty, value); } + } + + public event EventHandler Swiped; + public event EventHandler StartedDragging; + public event EventHandler Dragging; + public event EventHandler FinishedDragging; + public event EventHandler NoMoreCards; + + public CardStackView() + { + InitializeComponent(); + + var panGesture = new PanGestureRecognizer(); + panGesture.PanUpdated += OnPanUpdated; + + CardStack.GestureRecognizers.Add(panGesture); + + MessagingCenter.Subscribe(this, "UP", async (arg) => + { + Debug.WriteLine($"FakeCompleted: {_cardDistance}"); + + await HandleTouchCompleted(); + }); + + /* put this in MainActivity for workaround to work -> https://github.com/xamarin/Xamarin.Forms/issues/1495 public override bool DispatchTouchEvent(MotionEvent ev) { @@ -92,304 +92,265 @@ public override bool DispatchTouchEvent(MotionEvent ev) } */ - } - - public void Setup() - { - CardStack.Children.Clear(); - - if (ItemsSource != null && ItemsSource.Count == 0) - return; - - for (var i = NumberOfCards - 1; i >= 0; i--) - { - var cardView = new CardView(ItemTemplate) - { - IsVisible = false, - Scale = (i == 0) ? 1 : _defaultSubcardScale, - TranslationX = (i == 0) ? 0 : _defaultSubcardTranslationX, - Opacity = (i == 0) ? 0 : _defaultSubcardOpacity - }; - - CardStack.Children.Add(cardView, Constraint.Constant(0), Constraint.Constant(0), Constraint.RelativeToParent((parent) => { return parent.Width; }), - Constraint.RelativeToParent((parent) => { return parent.Height; }) - ); - } - - _itemIndex = 0; - - ShowNextCard(); - } - - private static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue) - { - if (newValue is INotifyCollectionChanged) - { - if (CollectionChangedEventHandler != null) - ((INotifyCollectionChanged)newValue).CollectionChanged -= CollectionChangedEventHandler; + } - CollectionChangedEventHandler = (sender, e) => ItemsSource_CollectionChanged(sender, e, (CardStackView)bindable); + public void Setup() + { + CardStack.Children.Clear(); - ((INotifyCollectionChanged)newValue).CollectionChanged += CollectionChangedEventHandler; - } + if (ItemsSource != null && ItemsSource.Count == 0) + return; - ((CardStackView)bindable).Setup(); - } + for (var i = NumberOfCards - 1; i >= 0; i--) + { + var cardView = new CardView(ItemTemplate) + { + IsVisible = false, + Scale = (i == 0) ? 1 : _defaultSubcardScale, + TranslationX = (i == 0) ? 0 : _defaultSubcardTranslationX, + Opacity = (i == 0) ? 0 : _defaultSubcardOpacity + }; - private static void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e, CardStackView cardStackView) - { - cardStackView.Setup(); - } + CardStack.Children.Add(cardView, Constraint.Constant(0), Constraint.Constant(0), Constraint.RelativeToParent((parent) => { return parent.Width; }), + Constraint.RelativeToParent((parent) => { return parent.Height; }) + ); + } - protected override void OnSizeAllocated(double width, double height) - { - base.OnSizeAllocated(width, height); + _itemIndex = 0; - if (CardMoveDistance == -1 && !width.Equals(-1)) - CardMoveDistance = (int)(width / 3); - } + ShowNextCard(); + } - private async void OnPanUpdated(object sender, PanUpdatedEventArgs e) - { - switch (e.StatusType) - { - case GestureStatus.Started: + private static void OnItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if (newValue is INotifyCollectionChanged) + { + if (CollectionChangedEventHandler != null) + ((INotifyCollectionChanged)newValue).CollectionChanged -= CollectionChangedEventHandler; - HandleTouchStart(); + CollectionChangedEventHandler = (sender, e) => ItemsSource_CollectionChanged(sender, e, (CardStackView)bindable); - Debug.WriteLine($"Started: {_cardDistance}, x:{e.TotalX} y:{e.TotalY}"); - break; + ((INotifyCollectionChanged)newValue).CollectionChanged += CollectionChangedEventHandler; + } - case GestureStatus.Running: + ((CardStackView)bindable).Setup(); + } - HandleTouchRunning((float)e.TotalX); + private static void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e, CardStackView cardStackView) + { + cardStackView.Setup(); + } - Debug.WriteLine($"Running: {_cardDistance}, x:{e.TotalX} y:{e.TotalY}, -> _lastX: {_lastX}"); - break; + protected override void OnSizeAllocated(double width, double height) + { + base.OnSizeAllocated(width, height); - case GestureStatus.Completed: + if (CardMoveDistance == -1 && !width.Equals(-1)) + CardMoveDistance = (int)(width / 3); + } - await HandleTouchCompleted(); + private async void OnPanUpdated(object sender, PanUpdatedEventArgs e) + { + switch (e.StatusType) + { + case GestureStatus.Started: - Debug.WriteLine($"Completed: {_cardDistance}, x:{e.TotalX} y:{e.TotalY}"); - break; + HandleTouchStart(); - case GestureStatus.Canceled: - break; - } - } - - private bool _isDragging; - private double _lastX; - private const double DeltaX = 100; + Debug.WriteLine($"Started: {_cardDistance}, x:{e.TotalX} y:{e.TotalY}"); + break; - private void HandleTouchStart() - { - if (_itemIndex >= ItemsSource.Count) - return; + case GestureStatus.Running: - if (_cardDistance != 0) - return; - - _lastX = 0; + HandleTouchRunning((float)e.TotalX); - _isDragging = true; + Debug.WriteLine($"Running: {_cardDistance}, x:{e.TotalX} y:{e.TotalY}, -> _lastX: {_lastX}"); + break; - StartedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], 0)); - } + case GestureStatus.Completed: - private void HandleTouchRunning(float horizontalTraslation) - { - if (_itemIndex >= ItemsSource.Count) - return; - - if (!_isDragging) - return; - - if (horizontalTraslation - _lastX > DeltaX) - return; - - _lastX = horizontalTraslation; - - var topCard = CardStack.Children[NumberOfCards - 1]; - var backCard = CardStack.Children[NumberOfCards - 2]; + await HandleTouchCompleted(); - if (topCard.IsVisible) - { - topCard.TranslationX = horizontalTraslation; + Debug.WriteLine($"Completed: {_cardDistance}, x:{e.TotalX} y:{e.TotalY}"); + break; - var rotationAngle = (float)(0.3f * Math.Min(horizontalTraslation / Width, 1.0f)); - topCard.Rotation = rotationAngle * 57.2957795f; + case GestureStatus.Canceled: + break; + } + } - _cardDistance = horizontalTraslation; + private bool _isDragging; + private double _lastX; + private const double DeltaX = 100; - Dragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], _cardDistance)); - } + private void HandleTouchStart() + { + if (_itemIndex >= ItemsSource.Count) + return; - backCard.Scale = Math.Min(_defaultSubcardScale + Math.Abs((_cardDistance / CardMoveDistance) * (1 - _defaultSubcardScale)), 1); - backCard.TranslationX = Math.Min(_defaultSubcardTranslationX + Math.Abs((_cardDistance / CardMoveDistance) * _defaultSubcardTranslationX), 0); - backCard.Opacity = Math.Min(_defaultSubcardOpacity + Math.Abs((_cardDistance / CardMoveDistance) * _defaultSubcardOpacity), 1); - } + if (_cardDistance != 0) + return; - private async Task HandleTouchCompleted() - { - if (_itemIndex >= ItemsSource.Count || !_isDragging) - return; - - _lastX = 0; - _isDragging = false; - - var topCard = CardStack.Children[NumberOfCards - 1]; - var backCard = CardStack.Children[NumberOfCards - 2]; - - if (Math.Abs(_cardDistance) >= CardMoveDistance) - { - await topCard.TranslateTo(_cardDistance > 0 ? Width * 2 : -Width * 2, 0, DefaultAnimationLength, Easing.SinIn); - - topCard.IsVisible = false; - - if (_cardDistance > 0) - { - Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[_itemIndex], SwipeDirection.Right)); - - if (SwipedRightCommand != null && SwipedRightCommand.CanExecute(ItemsSource[_itemIndex])) - SwipedRightCommand.Execute(ItemsSource[_itemIndex]); - } - else - { - Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[_itemIndex], SwipeDirection.Left)); - - if (SwipedLeftCommand != null && SwipedLeftCommand.CanExecute(ItemsSource[_itemIndex])) - SwipedLeftCommand.Execute(ItemsSource[_itemIndex]); - } - - _itemIndex++; - - ShowNextCard(); - } - else - { - await Task.WhenAll( - - topCard.TranslateTo((-topCard.X), -topCard.Y, DefaultAnimationLength, Easing.SpringOut), - topCard.RotateTo(0, DefaultAnimationLength, Easing.SpringOut), - backCard.ScaleTo(_defaultSubcardScale, DefaultAnimationLength, Easing.SpringOut), - backCard.TranslateTo(_defaultSubcardTranslationX, 0, DefaultAnimationLength, Easing.SpringOut), - backCard.FadeTo(_defaultSubcardOpacity, DefaultAnimationLength, Easing.SpringOut) - ); - } - - _cardDistance = 0; + _lastX = 0; - if (_itemIndex < ItemsSource.Count) - FinishedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], _cardDistance)); - } + _isDragging = true; - public async Task Swipe(SwipeDirection direction) - { - if (_itemIndex >= ItemsSource.Count) - return; + StartedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], 0)); + } - _lastX = 0; - _isDragging = false; - - var topCard = CardStack.Children[NumberOfCards - 1]; - var backCard = CardStack.Children[NumberOfCards - 2]; - - await Task.WhenAll( - topCard.TranslateTo(direction == SwipeDirection.Right ? Width * 2 : -Width * 2, 0, DefaultAnimationLength, Easing.SinIn), - backCard.ScaleTo(1, DefaultAnimationLength, Easing.SpringOut), - backCard.TranslateTo(0, 0, DefaultAnimationLength, Easing.SpringOut), - backCard.FadeTo(1, DefaultAnimationLength, Easing.SpringOut) - ); - - topCard.IsVisible = false; + private void HandleTouchRunning(float horizontalTraslation) + { + if (_itemIndex >= ItemsSource.Count) + return; - _itemIndex++; - - ShowNextCard(); - - _cardDistance = 0; - } + if (!_isDragging) + return; - private void ShowNextCard() - { - if (ItemsSource == null || ItemsSource?.Count == 0) - return; + if (Math.Abs(horizontalTraslation - _lastX) > DeltaX) + return; - var topCard = CardStack.Children[NumberOfCards - 1]; + _lastX = horizontalTraslation; - if (_itemIndex != 0) - { - CardStack.Children.Remove(topCard); + var topCard = CardStack.Children[NumberOfCards - 1]; + var backCard = CardStack.Children[NumberOfCards - 2]; - topCard.Scale = _defaultSubcardScale; - CardStack.Children.Insert(0, topCard); - } + if (topCard.IsVisible) + { + topCard.TranslationX = horizontalTraslation; - for (var i = NumberOfCards - 1; i >= 0; i--) - { - var cardView = (CardView)CardStack.Children[i]; + var rotationAngle = (float)(0.3f * Math.Min(horizontalTraslation / Width, 1.0f)); + topCard.Rotation = rotationAngle * 57.2957795f; - cardView.Rotation = 0; - cardView.TranslationX = _defaultSubcardTranslationX * (NumberOfCards - 1 - i); - cardView.Opacity = i == NumberOfCards - 1 ? 1 : _defaultSubcardOpacity; + _cardDistance = horizontalTraslation; - var index = Math.Min((NumberOfCards - 1), ItemsSource.Count) - i + _itemIndex; + Dragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], _cardDistance)); + } - if (ItemsSource.Count > index) - { - cardView.Update(ItemsSource[index]); - - if (!cardView.IsVisible) - { - cardView.TranslationX = -cardView.Width + _defaultSubcardTranslationX; - cardView.IsVisible = true; - - cardView.TranslateTo(_defaultSubcardTranslationX * (NumberOfCards - 1 - i), 0, DefaultAnimationLength, Easing.Linear); - } - } - } - } - - public async Task Swipe(SwipeDirection direction, uint animationLength = DefaultAnimationLength) - { - if (_itemIndex >= ItemsSource?.Count) - { - NoMoreCards?.Invoke(this, new EventArgs()); - return; - } - - var topCard = CardStack.Children[NumberOfCards - 1]; - var backCard = CardStack.Children[NumberOfCards - 2]; - - Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[_itemIndex], direction)); - - if (direction == SwipeDirection.Left) - { - if (SwipedLeftCommand != null && SwipedLeftCommand.CanExecute(ItemsSource[_itemIndex])) - SwipedLeftCommand.Execute(ItemsSource[_itemIndex]); - } - else if (direction == SwipeDirection.Right) - { - if (SwipedRightCommand != null && SwipedRightCommand.CanExecute(ItemsSource[_itemIndex])) - SwipedRightCommand.Execute(ItemsSource[_itemIndex]); - } - - _itemIndex++; - - await Task.WhenAll( - - topCard.TranslateTo(direction == SwipeDirection.Right ? Width * 2 : -Width * 2, 0, animationLength, Easing.SinIn), - topCard.RotateTo(direction == SwipeDirection.Right ? 17.18873385f : -17.18873385f, animationLength, Easing.SinIn), - backCard.ScaleTo(1.0f, animationLength), - backCard.TranslateTo(0, 0, animationLength, Easing.SinIn), - backCard.FadeTo(1, animationLength, Easing.SinIn) - ); - - topCard.IsVisible = false; - - ShowNextCard(); - } - } + backCard.Scale = Math.Min(_defaultSubcardScale + Math.Abs((_cardDistance / CardMoveDistance) * (1 - _defaultSubcardScale)), 1); + backCard.TranslationX = Math.Min(_defaultSubcardTranslationX + Math.Abs((_cardDistance / CardMoveDistance) * _defaultSubcardTranslationX), 0); + backCard.Opacity = Math.Min(_defaultSubcardOpacity + Math.Abs((_cardDistance / CardMoveDistance) * _defaultSubcardOpacity), 1); + } + + private async Task HandleTouchCompleted() + { + if (_itemIndex >= ItemsSource.Count || !_isDragging) + return; + + _lastX = 0; + _isDragging = false; + + var topCard = CardStack.Children[NumberOfCards - 1]; + var backCard = CardStack.Children[NumberOfCards - 2]; + + if (Math.Abs(_cardDistance) >= CardMoveDistance) + { + await topCard.TranslateTo(_cardDistance > 0 ? Width * 2 : -Width * 2, 0, DefaultAnimationLength, Easing.SinIn); + + topCard.IsVisible = false; + + if (_cardDistance > 0) + { + Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[_itemIndex], SwipeDirection.Right)); + + if (SwipedRightCommand != null && SwipedRightCommand.CanExecute(ItemsSource[_itemIndex])) + SwipedRightCommand.Execute(ItemsSource[_itemIndex]); + } + else + { + Swiped?.Invoke(this, new SwipedEventArgs(ItemsSource[_itemIndex], SwipeDirection.Left)); + + if (SwipedLeftCommand != null && SwipedLeftCommand.CanExecute(ItemsSource[_itemIndex])) + SwipedLeftCommand.Execute(ItemsSource[_itemIndex]); + } + + + _itemIndex++; + + ShowNextCard(); + } + else + { + await Task.WhenAll( + + topCard.TranslateTo((-topCard.X), -topCard.Y, DefaultAnimationLength, Easing.SpringOut), + topCard.RotateTo(0, DefaultAnimationLength, Easing.SpringOut), + backCard.ScaleTo(_defaultSubcardScale, DefaultAnimationLength, Easing.SpringOut), + backCard.TranslateTo(_defaultSubcardTranslationX, 0, DefaultAnimationLength, Easing.SpringOut), + backCard.FadeTo(_defaultSubcardOpacity, DefaultAnimationLength, Easing.SpringOut) + ); + } + + _cardDistance = 0; + + if (_itemIndex < ItemsSource.Count) + FinishedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], _cardDistance)); + } + + public async Task Swipe(SwipeDirection direction) + { + if (_itemIndex >= ItemsSource.Count) + return; + + _lastX = 0; + _isDragging = false; + + var topCard = CardStack.Children[NumberOfCards - 1]; + var backCard = CardStack.Children[NumberOfCards - 2]; + + await Task.WhenAll( + topCard.TranslateTo(direction == SwipeDirection.Right ? Width * 2 : -Width * 2, 0, DefaultAnimationLength, Easing.SinIn), + backCard.ScaleTo(1, DefaultAnimationLength, Easing.SpringOut), + backCard.TranslateTo(0, 0, DefaultAnimationLength, Easing.SpringOut), + backCard.FadeTo(1, DefaultAnimationLength, Easing.SpringOut) + ); + + topCard.IsVisible = false; + + _itemIndex++; + + ShowNextCard(); + + _cardDistance = 0; + } + + private void ShowNextCard() + { + if (ItemsSource == null || ItemsSource?.Count == 0) + return; + + var topCard = CardStack.Children[NumberOfCards - 1]; + + if (_itemIndex != 0) + { + CardStack.Children.Remove(topCard); + + topCard.Scale = _defaultSubcardScale; + CardStack.Children.Insert(0, topCard); + } + + for (var i = NumberOfCards - 1; i >= 0; i--) + { + var cardView = (CardView)CardStack.Children[i]; + + cardView.Rotation = 0; + cardView.TranslationX = _defaultSubcardTranslationX * (NumberOfCards - 1 - i); + cardView.Opacity = i == NumberOfCards - 1 ? 1 : _defaultSubcardOpacity; + + var index = Math.Min((NumberOfCards - 1), ItemsSource.Count) - i + _itemIndex; + + if (ItemsSource.Count > index) + { + cardView.Update(ItemsSource[index]); + + if (!cardView.IsVisible) + { + cardView.TranslationX = -cardView.Width + _defaultSubcardTranslationX; + cardView.IsVisible = true; + + cardView.TranslateTo(_defaultSubcardTranslationX * (NumberOfCards - 1 - i), 0, DefaultAnimationLength, Easing.Linear); + } + } + } + } + } } From 52db0cf20661aab9413aee02144abb7f3d97ebc3 Mon Sep 17 00:00:00 2001 From: Marco Bellini Date: Thu, 31 May 2018 14:13:11 +0200 Subject: [PATCH 14/14] fix android --- .../Views/CardStackView.xaml.cs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/SwipeCards.Controls/Views/CardStackView.xaml.cs b/SwipeCards.Controls/Views/CardStackView.xaml.cs index a3d2e04..1df532a 100644 --- a/SwipeCards.Controls/Views/CardStackView.xaml.cs +++ b/SwipeCards.Controls/Views/CardStackView.xaml.cs @@ -66,18 +66,20 @@ public ICommand SwipedLeftCommand public CardStackView() { InitializeComponent(); - - var panGesture = new PanGestureRecognizer(); - panGesture.PanUpdated += OnPanUpdated; - - CardStack.GestureRecognizers.Add(panGesture); - - MessagingCenter.Subscribe(this, "UP", async (arg) => + + if (Device.RuntimePlatform == Device.iOS) { - Debug.WriteLine($"FakeCompleted: {_cardDistance}"); + var panGesture = new PanGestureRecognizer(); + panGesture.PanUpdated += OnPanUpdated; + CardStack.GestureRecognizers.Add(panGesture); + } - await HandleTouchCompleted(); - }); + //MessagingCenter.Subscribe(this, "UP", async (arg) => + //{ + // Debug.WriteLine($"FakeCompleted: {_cardDistance}"); + // + // await HandleTouchCompleted(); + //}); /* put this in MainActivity for workaround to work -> https://github.com/xamarin/Xamarin.Forms/issues/1495 @@ -183,7 +185,7 @@ private async void OnPanUpdated(object sender, PanUpdatedEventArgs e) private double _lastX; private const double DeltaX = 100; - private void HandleTouchStart() + public void HandleTouchStart() { if (_itemIndex >= ItemsSource.Count) return; @@ -198,7 +200,7 @@ private void HandleTouchStart() StartedDragging?.Invoke(this, new DraggingEventArgs(ItemsSource[_itemIndex], 0)); } - private void HandleTouchRunning(float horizontalTraslation) + public void HandleTouchRunning(float horizontalTraslation) { if (_itemIndex >= ItemsSource.Count) return; @@ -231,8 +233,11 @@ private void HandleTouchRunning(float horizontalTraslation) backCard.Opacity = Math.Min(_defaultSubcardOpacity + Math.Abs((_cardDistance / CardMoveDistance) * _defaultSubcardOpacity), 1); } - private async Task HandleTouchCompleted() - { + public async Task HandleTouchCompleted() + { + if (_itemIndex >= ItemsSource.Count - 1) + NoMoreCards?.Invoke(this, new EventArgs()); + if (_itemIndex >= ItemsSource.Count || !_isDragging) return;