-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ab463b6
commit 9bdd061
Showing
98 changed files
with
8,077 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#Introduction | ||
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project. | ||
|
||
#Getting Started | ||
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about: | ||
1. Installation process | ||
2. Software dependencies | ||
3. Latest releases | ||
4. API references | ||
|
||
#Build and Test | ||
TODO: Describe and show how to build your code and run the tests. | ||
|
||
#Contribute | ||
TODO: Explain how other users and developers can contribute to make your code better. | ||
|
||
If you want to learn more about creating good readme files then refer the following [guidelines](https://www.visualstudio.com/en-us/docs/git/create-a-readme). You can also seek inspiration from the below readme files: | ||
- [ASP.NET Core](https://github.com/aspnet/Home) | ||
- [Visual Studio Code](https://github.com/Microsoft/vscode) | ||
- [Chakra Core](https://github.com/Microsoft/ChakraCore) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace SwipeCards.Controls.Models | ||
{ | ||
public class Card | ||
{ | ||
public string Text { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
|
||
using Xamarin.Forms; | ||
|
||
namespace SwipeCards.Controls | ||
{ | ||
public class MyPage : ContentPage | ||
{ | ||
public MyPage() | ||
{ | ||
var button = new Button | ||
{ | ||
Text = "Click Me!", | ||
VerticalOptions = LayoutOptions.CenterAndExpand, | ||
HorizontalOptions = LayoutOptions.CenterAndExpand, | ||
}; | ||
|
||
int clicked = 0; | ||
button.Clicked += (s, e) => button.Text = "Clicked: " + clicked++; | ||
|
||
Content = button; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using System.Reflection; | ||
using System.Runtime.CompilerServices; | ||
|
||
// Information about this assembly is defined by the following attributes. | ||
// Change them to the values specific to your project. | ||
|
||
[assembly: AssemblyTitle("SwipeCards.Controls")] | ||
[assembly: AssemblyDescription("")] | ||
[assembly: AssemblyConfiguration("")] | ||
[assembly: AssemblyCompany("")] | ||
[assembly: AssemblyProduct("")] | ||
[assembly: AssemblyCopyright("(c) Robin-Manuel Thiel")] | ||
[assembly: AssemblyTrademark("")] | ||
[assembly: AssemblyCulture("")] | ||
|
||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". | ||
// The form "{Major}.{Minor}.*" will automatically update the build and revision, | ||
// and "{Major}.{Minor}.{Build}.*" will update just the revision. | ||
|
||
[assembly: AssemblyVersion("1.0.*")] | ||
|
||
// The following attributes are used to specify the signing key for the assembly, | ||
// if desired. See the Mono documentation for more information about signing. | ||
|
||
//[assembly: AssemblyDelaySign(false)] | ||
//[assembly: AssemblyKeyFile("")] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
<PropertyGroup> | ||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | ||
<ProjectGuid>{73CC4C24-1BD0-49E3-AE34-360D404B8BA0}</ProjectGuid> | ||
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> | ||
<UseMSBuildEngine>true</UseMSBuildEngine> | ||
<OutputType>Library</OutputType> | ||
<RootNamespace>SwipeCards.Controls</RootNamespace> | ||
<AssemblyName>SwipeCards.Controls</AssemblyName> | ||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | ||
<TargetFrameworkProfile>Profile111</TargetFrameworkProfile> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | ||
<DebugSymbols>true</DebugSymbols> | ||
<DebugType>full</DebugType> | ||
<Optimize>false</Optimize> | ||
<OutputPath>bin\Debug</OutputPath> | ||
<DefineConstants>DEBUG;</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | ||
<Optimize>true</Optimize> | ||
<OutputPath>bin\Release</OutputPath> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Compile Include="Properties\AssemblyInfo.cs" /> | ||
<Compile Include="Models\Card.cs" /> | ||
<Compile Include="Views\CardStackView.xaml.cs"> | ||
<DependentUpon>..\..\SwipeCards.Demo.Forms\Views\CardStackView.xaml</DependentUpon> | ||
</Compile> | ||
<Compile Include="Views\CardView.xaml.cs"> | ||
<DependentUpon>..\..\SwipeCards.Demo.Forms\Views\CardView.xaml</DependentUpon> | ||
</Compile> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<Reference Include="Xamarin.Forms.Core"> | ||
<HintPath>..\packages\Xamarin.Forms.2.3.3.193\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll</HintPath> | ||
</Reference> | ||
<Reference Include="Xamarin.Forms.Platform"> | ||
<HintPath>..\packages\Xamarin.Forms.2.3.3.193\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll</HintPath> | ||
</Reference> | ||
<Reference Include="Xamarin.Forms.Xaml"> | ||
<HintPath>..\packages\Xamarin.Forms.2.3.3.193\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll</HintPath> | ||
</Reference> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<None Include="packages.config" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<Folder Include="Models\" /> | ||
<Folder Include="Views\" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<EmbeddedResource Include="Views\CardStackView.xaml"> | ||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator> | ||
</EmbeddedResource> | ||
<EmbeddedResource Include="Views\CardView.xaml"> | ||
<Generator>MSBuild:UpdateDesignTimeXaml</Generator> | ||
</EmbeddedResource> | ||
</ItemGroup> | ||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> | ||
<Import Project="..\packages\Xamarin.Forms.2.3.3.193\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\packages\Xamarin.Forms.2.3.3.193\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" /> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<ContentView | ||
x:Class="SwipeCards.Controls.CardStackView" | ||
xmlns="http://xamarin.com/schemas/2014/forms" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
Padding="20"> | ||
|
||
<Grid> | ||
<RelativeLayout x:Name="CardStack" /> | ||
|
||
<!-- | ||
HACK: The TouchObserber is a terrible hack, so let me explain the problem | ||
On Android, Child elements obserb touch events and don't pass them to the sender. | ||
So when attaching the PanGestureRecognizer to the parent view, it won't fire, when dragging the child. | ||
To fix this temporary, I added this transparent view on top of all others and attach the PanGestureRecognizer to it. | ||
A custom view renderer with an "IgnoreTouch" property might be a smarter solution... | ||
--> | ||
<BoxView x:Name="TouchObserber" BackgroundColor="Transparent" /> | ||
</Grid> | ||
|
||
</ContentView> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using Xamarin.Forms; | ||
using SwipeCards.Controls.Models; | ||
using System.Collections; | ||
using System.Threading.Tasks; | ||
using System.Windows.Input; | ||
|
||
namespace SwipeCards.Controls | ||
{ | ||
public partial class CardStackView : ContentView | ||
{ | ||
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(CardStackView), null, propertyChanged: (bindable, oldValue, newValue) => ((CardStackView)bindable).Setup()); | ||
public IList ItemsSource | ||
{ | ||
get { return (IList)GetValue(ItemsSourceProperty); } | ||
set { SetValue(ItemsSourceProperty, value); } | ||
} | ||
|
||
public static readonly BindableProperty CardMoveDistanceProperty = BindableProperty.Create(nameof(CardMoveDistance), typeof(int), typeof(CardStackView), 0); | ||
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); } | ||
} | ||
|
||
// Called when a card is swiped left/right | ||
public Action<object> SwipedRight = null; | ||
public Action<object> SwipedLeft = null; | ||
|
||
|
||
|
||
private const int numberOfCards = 2; | ||
private const int animationLength = 250; | ||
|
||
private CardView[] cardViews = new CardView[numberOfCards]; | ||
private float defaultSubcardScale = 0.8f; | ||
private float cardDistance = 0; | ||
private int itemIndex = 0; | ||
|
||
public CardStackView() | ||
{ | ||
InitializeComponent(); | ||
|
||
// Add two cards to 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(i); | ||
cardView.IsVisible = false; | ||
cardViews[i] = cardView; | ||
cardView.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 | ||
); | ||
} | ||
|
||
// Register pan gesture | ||
var panGesture = new PanGestureRecognizer(); | ||
panGesture.PanUpdated += OnPanUpdated; | ||
TouchObserber.GestureRecognizers.Add(panGesture); | ||
} | ||
|
||
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; | ||
} | ||
} | ||
|
||
private void Setup() | ||
{ | ||
itemIndex = 0; | ||
ShowNextCard(); | ||
} | ||
|
||
protected override void OnSizeAllocated(double width, double height) | ||
{ | ||
base.OnSizeAllocated(width, height); | ||
|
||
// Recalculate move distance | ||
CardMoveDistance = (int)(width / 3); | ||
} | ||
|
||
private void HandleTouchRunning(float xDiff) | ||
{ | ||
var topCard = cardViews[0]; | ||
var backCard = cardViews[1]; | ||
|
||
// 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 its moved | ||
cardDistance = xDiff; | ||
} | ||
|
||
// Scale the backcard | ||
if (backCard.IsVisible) | ||
{ | ||
backCard.Scale = Math.Min(defaultSubcardScale + Math.Abs((cardDistance / CardMoveDistance) * (1.0f - defaultSubcardScale)), 1.0f); | ||
} | ||
} | ||
|
||
private async Task HandleTouchCompleted() | ||
{ | ||
var topCard = cardViews[0]; | ||
var backCard = cardViews[1]; | ||
|
||
// 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 : -this.Width, 0, animationLength / 2, Easing.SpringOut); | ||
topCard.IsVisible = false; | ||
|
||
// Fire events | ||
if (cardDistance > 0) | ||
{ | ||
if (SwipedRight != null) | ||
SwipedRight(topCard.Item); | ||
if (SwipedRightCommand != null && SwipedRightCommand.CanExecute(topCard.Item)) | ||
SwipedRightCommand.Execute(topCard.Item); | ||
} | ||
else | ||
{ | ||
if (SwipedLeft != null) | ||
SwipedLeft(topCard.Item); | ||
if (SwipedLeftCommand != null && SwipedLeftCommand.CanExecute(topCard.Item)) | ||
SwipedLeftCommand.Execute(topCard.Item); | ||
} | ||
|
||
// Next card | ||
itemIndex++; | ||
ShowNextCard(); | ||
} | ||
else | ||
{ | ||
// Move card back to the center | ||
var traslateAnmimation = topCard.TranslateTo((-topCard.X), -topCard.Y, animationLength, Easing.SpringOut); | ||
var rotateAnimation = topCard.RotateTo(0, animationLength, Easing.SpringOut); | ||
|
||
// Scale the back card down | ||
var scaleAnimation = backCard.ScaleTo(defaultSubcardScale, animationLength, Easing.SpringOut); | ||
|
||
await Task.WhenAll(new List<Task> { traslateAnmimation, rotateAnimation, scaleAnimation }); | ||
} | ||
} | ||
|
||
private void ShowNextCard() | ||
{ | ||
for (int i = 0; i < Math.Min(numberOfCards, ItemsSource.Count); i++) | ||
{ | ||
var cardView = cardViews[i]; | ||
cardView.IsVisible = false; | ||
cardView.Scale = (i == 0) ? 1.0f : defaultSubcardScale; | ||
cardView.Rotation = 0; | ||
cardView.TranslationX = 0; | ||
|
||
// Check if next item is available | ||
if (itemIndex + i < ItemsSource.Count) | ||
{ | ||
cardView.Item = ItemsSource[itemIndex + i]; | ||
cardView.UpdateUi(); | ||
|
||
cardView.IsVisible = true; | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.