diff --git a/example/async_data/lib/main.dart b/example/async_data/lib/main.dart index 6071cdc..7648233 100644 --- a/example/async_data/lib/main.dart +++ b/example/async_data/lib/main.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; - import 'package:flutter_tindercard/flutter_tindercard.dart'; void main() => runApp(MyApp()); @@ -110,20 +109,17 @@ class _AsyncDataExampleHomePageState extends State orientation: AmassOrientation.bottom, totalNum: imageList.length, stackNum: 4, - swipeEdge: 4.0, maxWidth: MediaQuery.of(context).size.width * 0.9, maxHeight: MediaQuery.of(context).size.width * 0.9, - minWidth: MediaQuery.of(context).size.width * 0.8, - minHeight: MediaQuery.of(context).size.width * 0.8, cardBuilder: (context, index) => Card( child: Image.asset('${imageList[index]}'), ), cardController: controller = CardController(), - swipeUpdateCallback: (DragUpdateDetails details, Alignment align) { + swipeUpdateCallback: (DragUpdateDetails details, Offset offset) { /// Get swiping card's alignment - if (align.x < 0) { + if (offset.dx < 0) { //Card is LEFT swiping - } else if (align.x > 0) { + } else if (offset.dx > 0) { //Card is RIGHT swiping } }, diff --git a/example/example/lib/main.dart b/example/example/lib/main.dart index 8b62c5a..6b59db1 100644 --- a/example/example/lib/main.dart +++ b/example/example/lib/main.dart @@ -47,20 +47,17 @@ class _ExampleHomePageState extends State orientation: AmassOrientation.bottom, totalNum: welcomeImages.length, stackNum: 3, - swipeEdge: 4.0, maxWidth: MediaQuery.of(context).size.width * 0.9, maxHeight: MediaQuery.of(context).size.width * 0.9, - minWidth: MediaQuery.of(context).size.width * 0.8, - minHeight: MediaQuery.of(context).size.width * 0.8, cardBuilder: (context, index) => Card( child: Image.asset('${welcomeImages[index]}'), ), cardController: controller = CardController(), - swipeUpdateCallback: (DragUpdateDetails details, Alignment align) { + swipeUpdateCallback: (DragUpdateDetails details, Offset offset) { /// Get swiping card's alignment - if (align.x < 0) { + if (offset.dx < 0) { //Card is LEFT swiping - } else if (align.x > 0) { + } else if (offset.dx > 0) { //Card is RIGHT swiping } }, diff --git a/lib/flutter_tindercard.dart b/lib/flutter_tindercard.dart index 0d9787c..2769013 100644 --- a/lib/flutter_tindercard.dart +++ b/lib/flutter_tindercard.dart @@ -32,7 +32,9 @@ class TinderSwapCard extends StatefulWidget { final CardController cardController; - final List _cardSizes = []; + // final List _cardSizes = []; + final Size _cardSize; + final List _cardScales = []; final List _cardAligns = []; @@ -53,14 +55,13 @@ class TinderSwapCard extends StatefulWidget { AmassOrientation orientation = AmassOrientation.bottom, int stackNum = 3, int animDuration = 800, - double swipeEdge = 3.0, - double swipeEdgeVertical = 8.0, + double swipeEdge = 75.0, + double swipeEdgeVertical = 100.0, bool swipeUp = false, bool swipeDown = false, double maxWidth, double maxHeight, - double minWidth, - double minHeight, + double scaleFactor = 0.8, bool allowVerticalMovement = true, this.cardController, this.swipeCompleteCallback, @@ -68,7 +69,6 @@ class TinderSwapCard extends StatefulWidget { }) : assert(stackNum > 1), assert(swipeEdge > 0), assert(swipeEdgeVertical > 0), - assert(maxWidth > minWidth && maxHeight > minHeight), _cardBuilder = cardBuilder, _totalNum = totalNum, _stackNum = stackNum, @@ -77,37 +77,28 @@ class TinderSwapCard extends StatefulWidget { _swipeEdgeVertical = swipeEdgeVertical, _swipeUp = swipeUp, _swipeDown = swipeDown, - _allowVerticalMovement = allowVerticalMovement { - final widthGap = maxWidth - minWidth; - final heightGap = maxHeight - minHeight; - - for (var i = 0; i < _stackNum; i++) { - _cardSizes.add( - Size(minWidth + (widthGap / _stackNum) * i, - minHeight + (heightGap / _stackNum) * i), - ); + _allowVerticalMovement = allowVerticalMovement, + _cardSize = Size(maxWidth, maxHeight) { + double scale = 1; + double dy = 0; + for (int i = 0; i < stackNum; i++) { + _cardScales.add(scale); switch (orientation) { - case AmassOrientation.bottom: + case AmassOrientation.top: _cardAligns.add( - Alignment( - 0.0, - (0.5 / (_stackNum - 1)) * (stackNum - i), - ), + Alignment(0.0, scale - (1 + dy)), ); break; - case AmassOrientation.top: + case AmassOrientation.bottom: _cardAligns.add( - Alignment( - 0.0, - (-0.5 / (_stackNum - 1)) * (stackNum - i), - ), + Alignment(0.0, 1 + dy - scale), ); break; case AmassOrientation.left: _cardAligns.add( Alignment( - (-0.5 / (_stackNum - 1)) * (stackNum - i), + -9 * (1 + dy - scale), 0.0, ), ); @@ -115,90 +106,94 @@ class TinderSwapCard extends StatefulWidget { case AmassOrientation.right: _cardAligns.add( Alignment( - (0.5 / (_stackNum - 1)) * (stackNum - i), - 0.0, + 9 * (1 + dy - scale), + 0, ), ); break; } + + scale *= scaleFactor; + dy += .05 * scale; } } } class _TinderSwapCardState extends State with TickerProviderStateMixin { - Alignment frontCardAlign; + Offset _dragPoint; - AnimationController _animationController; + Offset _offset = Offset.zero; - int _currentFront; + AnimationController _animationController; static TriggerDirection _trigger; - Widget _buildCard(BuildContext context, int realIndex) { - if (realIndex < 0) { - return Container(); + Widget _buildCard(BuildContext context, index) { + final Widget card = SizedBox.fromSize( + size: widget._cardSize, child: widget._cardBuilder(context, index)); + + // When user likes/dislikes by button rotate about the card centre + if (_dragPoint == null) { + _dragPoint = + Offset(widget._cardSize.width / 2, widget._cardSize.height / 2); } - final index = realIndex - _currentFront; - if (index == widget._stackNum - 1) { - return Align( - alignment: _animationController.status == AnimationStatus.forward - ? frontCardAlign = CardAnimation.frontCardAlign( - _animationController, - frontCardAlign, - widget._cardAligns[widget._stackNum - 1], - widget._swipeEdge, - widget._swipeUp, - widget._swipeDown, - ).value - : frontCardAlign, - child: Transform.rotate( - angle: (pi / 180.0) * - (_animationController.status == AnimationStatus.forward - ? CardAnimation.frontCardRota( - _animationController, frontCardAlign.x) - .value - : frontCardAlign.x), - child: SizedBox.fromSize( - size: widget._cardSizes[index], - child: widget._cardBuilder( - context, - widget._totalNum - realIndex - 1, - ), - ), - ), - ); + if (index == 0) { + // Rotate about the user's finger, the opposite end has resistance + final angle = (_dragPoint.dy > widget._cardSize.height / 2 ? -1 : 1) * + .001 * + (_animationController.status == AnimationStatus.forward + ? CardAnimation.frontCardRota(_animationController, _offset.dx, + endRot: _offset.dx / 2) + .value + : _offset.dx); + + _offset = _animationController.status == AnimationStatus.forward + ? CardAnimation.frontCardOffset( + _animationController, + _offset, + Offset(MediaQuery.of(context).size.width, + MediaQuery.of(context).size.height), + widget._swipeEdge, + widget._swipeUp, + widget._swipeDown) + .value + : _offset; + + final transformation = Matrix4.identity(); + transformation.translate(_offset.dx, _offset.dy); + transformation.translate(_dragPoint.dx, _dragPoint.dy); + transformation.rotateZ(angle); + transformation.translate(-_dragPoint.dx, -_dragPoint.dy); + + return Align(child: Transform(transform: transformation, child: card)); } + final prepareNextCard = _offset.dx > widget._swipeEdge || + _offset.dx < -widget._swipeEdge || + _offset.dy > widget._swipeEdgeVertical || + _offset.dy < -widget._swipeEdgeVertical; + return Align( alignment: _animationController.status == AnimationStatus.forward && - (frontCardAlign.x > 3.0 || - frontCardAlign.x < -3.0 || - frontCardAlign.y > 3 || - frontCardAlign.y < -3) + prepareNextCard ? CardAnimation.backCardAlign( _animationController, widget._cardAligns[index], - widget._cardAligns[index + 1], + widget._cardAligns[index - 1], ).value : widget._cardAligns[index], - child: SizedBox.fromSize( - size: _animationController.status == AnimationStatus.forward && - (frontCardAlign.x > 3.0 || - frontCardAlign.x < -3.0 || - frontCardAlign.y > 3 || - frontCardAlign.y < -3) - ? CardAnimation.backCardSize( + child: Transform.scale( + scale: _animationController.status == AnimationStatus.forward && + prepareNextCard + ? CardAnimation.backCardScale( _animationController, - widget._cardSizes[index], - widget._cardSizes[index + 1], + widget._cardScales[index], + widget._cardScales[index - 1], ).value - : widget._cardSizes[index], - child: widget._cardBuilder( - context, - widget._totalNum - realIndex - 1, - ), + : widget._cardScales[index], + child: card, ), ); } @@ -206,39 +201,31 @@ class _TinderSwapCardState extends State List _buildCards(BuildContext context) { final cards = []; - for (var i = _currentFront; i < _currentFront + widget._stackNum; i++) { + int i = min(widget._stackNum, widget._totalNum); + while (i-- != 0) { cards.add(_buildCard(context, i)); } cards.add(SizedBox.expand( child: GestureDetector( + onPanStart: (DragStartDetails details) { + _dragPoint = details.localPosition; + }, onPanUpdate: (final details) { setState(() { if (widget._allowVerticalMovement == true) { - frontCardAlign = Alignment( - frontCardAlign.x + - details.delta.dx * 20 / MediaQuery.of(context).size.width, - frontCardAlign.y + - details.delta.dy * 30 / MediaQuery.of(context).size.height, - ); + _offset += details.delta; + // print('offset: $_offset'); } else { - frontCardAlign = Alignment( - frontCardAlign.x + - details.delta.dx * 20 / MediaQuery.of(context).size.width, - 0, - ); - - if (widget.swipeUpdateCallback != null) { - widget.swipeUpdateCallback(details, frontCardAlign); - } + _offset = Offset(_offset.dx + details.delta.dx, 0); } if (widget.swipeUpdateCallback != null) { - widget.swipeUpdateCallback(details, frontCardAlign); + widget.swipeUpdateCallback(details, _offset); } }); }, - onPanEnd: (final details) { + onPanEnd: (final DragEndDetails details) { animateCards(TriggerDirection.none); }, ), @@ -247,8 +234,7 @@ class _TinderSwapCardState extends State } void animateCards(TriggerDirection trigger) { - if (_animationController.isAnimating || - _currentFront + widget._stackNum == 0) { + if (_animationController.isAnimating) { return; } _trigger = trigger; @@ -261,7 +247,7 @@ class _TinderSwapCardState extends State animateCards(trigger); } - // support for asynchronous data events + /// support for asynchronous data events @override void didUpdateWidget(covariant TinderSwapCard oldWidget) { super.didUpdateWidget(oldWidget); @@ -283,9 +269,7 @@ class _TinderSwapCardState extends State } void _initState() { - _currentFront = widget._totalNum - widget._stackNum; - - frontCardAlign = widget._cardAligns[widget._cardAligns.length - 1]; + _offset = Offset.zero; _animationController = AnimationController( vsync: this, @@ -298,29 +282,24 @@ class _TinderSwapCardState extends State _animationController.addStatusListener( (final status) { - final index = widget._totalNum - widget._stackNum - _currentFront; - if (status == AnimationStatus.completed) { CardSwipeOrientation orientation; - if (frontCardAlign.x < -widget._swipeEdge) { + if (_offset.dx < -widget._swipeEdge) { orientation = CardSwipeOrientation.left; - } else if (frontCardAlign.x > widget._swipeEdge) { + } else if (_offset.dx > widget._swipeEdge) { orientation = CardSwipeOrientation.right; - } else if (frontCardAlign.y < -widget._swipeEdgeVertical) { + } else if (_offset.dy < -widget._swipeEdgeVertical) { orientation = CardSwipeOrientation.up; - } else if (frontCardAlign.y > widget._swipeEdgeVertical) { + } else if (_offset.dy > widget._swipeEdgeVertical) { orientation = CardSwipeOrientation.down; } else { - frontCardAlign = widget._cardAligns[widget._stackNum - 1]; orientation = CardSwipeOrientation.recover; } if (widget.swipeCompleteCallback != null) { - widget.swipeCompleteCallback(orientation, index); + widget.swipeCompleteCallback(orientation); } - - if (orientation != CardSwipeOrientation.recover) changeCardOrder(); } }, ); @@ -330,14 +309,7 @@ class _TinderSwapCardState extends State Widget build(BuildContext context) { widget.cardController?.addListener(triggerSwap); - return Stack(children: _buildCards(context)); - } - - void changeCardOrder() { - setState(() { - _currentFront--; - frontCardAlign = widget._cardAligns[widget._stackNum - 1]; - }); + return Stack(clipBehavior: Clip.none, children: _buildCards(context)); } } @@ -348,48 +320,53 @@ enum CardSwipeOrientation { left, right, recover, up, down } /// swipe card to [CardSwipeOrientation.left] or [CardSwipeOrientation.right] /// , [CardSwipeOrientation.recover] means back to start. typedef CardSwipeCompleteCallback = void Function( - CardSwipeOrientation orientation, int index); + CardSwipeOrientation orientation); /// [DragUpdateDetails] of swiping card. typedef CardDragUpdateCallback = void Function( - DragUpdateDetails details, Alignment align); + DragUpdateDetails details, Offset offset); enum AmassOrientation { top, bottom, left, right } class CardAnimation { - static Animation frontCardAlign( + static Animation frontCardOffset( AnimationController controller, - Alignment beginAlign, - Alignment baseAlign, + Offset beginOffset, + Offset screenOffset, double swipeEdge, bool swipeUp, bool swipeDown, ) { double endX, endY; + // onPanEnd if (_TinderSwapCardState._trigger == TriggerDirection.none) { - endX = beginAlign.x > 0 - ? (beginAlign.x > swipeEdge ? beginAlign.x + 10.0 : baseAlign.x) - : (beginAlign.x < -swipeEdge ? beginAlign.x - 10.0 : baseAlign.x); - endY = beginAlign.x > 3.0 || beginAlign.x < -swipeEdge - ? beginAlign.y - : baseAlign.y; + // need to multiply screenOffset.dx so that the trailing corner doesn't hang around + endX = beginOffset.dx > 0 + ? (beginOffset.dx > swipeEdge ? screenOffset.dx * 1.5 : 0) + : (beginOffset.dx < -swipeEdge ? -screenOffset.dx * 1.5 : 0); + endY = beginOffset.dx > swipeEdge || beginOffset.dx < -swipeEdge + ? beginOffset.dy + : 0; if (swipeUp || swipeDown) { - if (beginAlign.y < 0) { + if (beginOffset.dy < 0) { if (swipeUp) { - endY = - beginAlign.y < -swipeEdge ? beginAlign.y - 10.0 : baseAlign.y; + endY = beginOffset.dy < -swipeEdge + ? beginOffset.dy - screenOffset.dy + : 0; } - } else if (beginAlign.y > 0) { + } else if (beginOffset.dy > 0) { if (swipeDown) { - endY = beginAlign.y > swipeEdge ? beginAlign.y + 10.0 : baseAlign.y; + endY = beginOffset.dy > swipeEdge + ? beginOffset.dy + screenOffset.dy + : 0; } } } } else if (_TinderSwapCardState._trigger == TriggerDirection.left) { - endX = beginAlign.x - swipeEdge; - endY = beginAlign.y + 0.5; + endX = -screenOffset.dx * 1.5; + endY = beginOffset.dy + 0.5; } /* Trigger Swipe Up or Down */ else if (_TinderSwapCardState._trigger == TriggerDirection.up || @@ -397,18 +374,23 @@ class CardAnimation { var beginY = _TinderSwapCardState._trigger == TriggerDirection.up ? -10 : 10; - endY = beginY < -swipeEdge ? beginY - 10.0 : baseAlign.y; + endY = beginY < -swipeEdge ? beginY - 10.0 : screenOffset.dy; - endX = beginAlign.x > 0 - ? (beginAlign.x > swipeEdge ? beginAlign.x + 10.0 : baseAlign.x) - : (beginAlign.x < -swipeEdge ? beginAlign.x - 10.0 : baseAlign.x); + endX = beginOffset.dx > 0 + ? (beginOffset.dx > swipeEdge + ? beginOffset.dx + 10.0 + : screenOffset.dx) + : (beginOffset.dx < -swipeEdge + ? beginOffset.dx - 10.0 + : screenOffset.dx); } else { - endX = beginAlign.x + swipeEdge; - endY = beginAlign.y + 0.5; + endX = screenOffset.dx * 1.5; + endY = beginOffset.dy + 0.5; } - return AlignmentTween( - begin: beginAlign, - end: Alignment(endX, endY), + + return Tween( + begin: beginOffset, + end: Offset(endX, endY), ).animate( CurvedAnimation( parent: controller, @@ -418,8 +400,9 @@ class CardAnimation { } static Animation frontCardRota( - AnimationController controller, double beginRot) { - return Tween(begin: beginRot, end: 0.0).animate( + AnimationController controller, double beginRot, + {double endRot = 0.0}) { + return Tween(begin: beginRot, end: endRot).animate( CurvedAnimation( parent: controller, curve: Curves.easeOut, @@ -427,12 +410,12 @@ class CardAnimation { ); } - static Animation backCardSize( + static Animation backCardScale( AnimationController controller, - Size beginSize, - Size endSize, + double beginScale, + double endScale, ) { - return SizeTween(begin: beginSize, end: endSize).animate( + return Tween(begin: beginScale, end: endScale).animate( CurvedAnimation( parent: controller, curve: Curves.easeOut,