feat: additional info to cardBuilder (#22)

Additional swiping info to cardBuilder callback
Add an additional callback containing the horizontal and vertical swipe direction 

Co-authored-by: samuel.peirson.knowunity <samuel.peirson@knowunity.com>
This commit is contained in:
sarope 2023-06-26 21:04:31 +02:00 committed by GitHub
parent f1ab70fd99
commit 1f23f08ea1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 115 additions and 21 deletions

View File

@ -1,5 +1,10 @@
## NEXT
## [6.0.0]
- Adds `onSwipeDirectionChange` callback containing the horizontal and vertical swipe direction
- **BREAKING CHANGE**:
- Modifies the `cardBuilder` callback, to include the ratio of horizontal drag to threshold as a percentage
and the ratio of vertical drag to threshold as a percentage.
- **BREAKING CHANGE**: - **BREAKING CHANGE**:
- `isHorizontalSwipingEnabled` and `isVerticalSwipingEnabled` have been removed. Use `allowedSwipeDirection` instead. - `isHorizontalSwipingEnabled` and `isVerticalSwipingEnabled` have been removed. Use `allowedSwipeDirection` instead.

View File

@ -84,7 +84,7 @@ class Example extends StatelessWidget {
body: Flexible( body: Flexible(
child: CardSwiper( child: CardSwiper(
cardsCount: cards.length, cardsCount: cards.length,
cardBuilder: (context, index) => cards[index], cardBuilder: (context, index, percentThresholdX, percentThresholdY) => cards[index],
), ),
), ),
); );
@ -97,7 +97,7 @@ class Example extends StatelessWidget {
#### Basic #### Basic
| Parameter | Default | Description | Required | | Parameter | Default | Description | Required |
|-------------------------------------------| :------------------------------------------------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------| :------: | |-------------------------------------------|:---------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------| :------: |
| cardBuilder | - | Widget builder for rendering cards | true | | cardBuilder | - | Widget builder for rendering cards | true |
| cardsCount | - | Number of cards | true | | cardsCount | - | Number of cards | true |
| controller | - | Controller to trigger swipe actions | false | | controller | - | Controller to trigger swipe actions | false |
@ -116,7 +116,7 @@ class Example extends StatelessWidget {
| padding | EdgeInsets.symmetric(horizontal: 20, vertical: 25) | The padding around the swiper | false | | padding | EdgeInsets.symmetric(horizontal: 20, vertical: 25) | The padding around the swiper | false |
| scale | 0.9 | Scale of the card that is behind the front card | false | | scale | 0.9 | Scale of the card that is behind the front card | false |
| threshold | 50 | Threshold from which the card is swiped away | false | | threshold | 50 | Threshold from which the card is swiped away | false |
| onSwipeDirectionChange | - | A callback containing the horizontal and vertical swipe direction | false |
#### Controller #### Controller
The `Controller` is used to swipe the card from outside of the widget. You can create a controller called `CardSwiperController` and save the instance for further usage. Please have a closer look at our [Example](https://github.com/ricardodalarme/flutter_card_swiper/tree/main/example) for the usage. The `Controller` is used to swipe the card from outside of the widget. You can create a controller called `CardSwiperController` and save the instance for further usage. Please have a closer look at our [Example](https://github.com/ricardodalarme/flutter_card_swiper/tree/main/example) for the usage.

View File

@ -47,7 +47,13 @@ class _ExamplePageState extends State<Example> {
numberOfCardsDisplayed: 3, numberOfCardsDisplayed: 3,
backCardOffset: const Offset(40, 40), backCardOffset: const Offset(40, 40),
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(24.0),
cardBuilder: (context, index) => cards[index], cardBuilder: (
context,
index,
horizontalThresholdPercentage,
verticalThresholdPercentage,
) =>
cards[index],
), ),
), ),
Padding( Padding(

View File

@ -13,6 +13,7 @@ class CardAnimation {
this.isHorizontalSwipingEnabled = true, this.isHorizontalSwipingEnabled = true,
this.isVerticalSwipingEnabled = true, this.isVerticalSwipingEnabled = true,
this.allowedSwipeDirection = const AllowedSwipeDirection.all(), this.allowedSwipeDirection = const AllowedSwipeDirection.all(),
this.onSwipeDirectionChanged,
}) : scale = initialScale; }) : scale = initialScale;
final double maxAngle; final double maxAngle;
@ -22,6 +23,7 @@ class CardAnimation {
final bool isHorizontalSwipingEnabled; final bool isHorizontalSwipingEnabled;
final bool isVerticalSwipingEnabled; final bool isVerticalSwipingEnabled;
final AllowedSwipeDirection allowedSwipeDirection; final AllowedSwipeDirection allowedSwipeDirection;
final ValueChanged<CardSwiperDirection>? onSwipeDirectionChanged;
double left = 0; double left = 0;
double top = 0; double top = 0;
@ -56,19 +58,41 @@ class CardAnimation {
void update(double dx, double dy, bool inverseAngle) { void update(double dx, double dy, bool inverseAngle) {
if (allowedSwipeDirection.right && allowedSwipeDirection.left) { if (allowedSwipeDirection.right && allowedSwipeDirection.left) {
if (left > 0) {
onSwipeDirectionChanged?.call(CardSwiperDirection.right);
} else if (left < 0) {
onSwipeDirectionChanged?.call(CardSwiperDirection.left);
}
left += dx; left += dx;
} else if (allowedSwipeDirection.right) { } else if (allowedSwipeDirection.right) {
if (left >= 0) left += dx; if (left >= 0) {
onSwipeDirectionChanged?.call(CardSwiperDirection.right);
left += dx;
}
} else if (allowedSwipeDirection.left) { } else if (allowedSwipeDirection.left) {
if (left <= 0) left += dx; if (left <= 0) {
onSwipeDirectionChanged?.call(CardSwiperDirection.left);
left += dx;
}
} }
if (allowedSwipeDirection.up && allowedSwipeDirection.down) { if (allowedSwipeDirection.up && allowedSwipeDirection.down) {
if (top > 0) {
onSwipeDirectionChanged?.call(CardSwiperDirection.bottom);
} else if (top < 0) {
onSwipeDirectionChanged?.call(CardSwiperDirection.top);
}
top += dy; top += dy;
} else if (allowedSwipeDirection.up) { } else if (allowedSwipeDirection.up) {
if (top <= 0) top += dy; if (top <= 0) {
onSwipeDirectionChanged?.call(CardSwiperDirection.top);
top += dy;
}
} else if (allowedSwipeDirection.down) { } else if (allowedSwipeDirection.down) {
if (top >= 0) top += dy; if (top >= 0) {
onSwipeDirectionChanged?.call(CardSwiperDirection.bottom);
top += dy;
}
} }
total = left + top; total = left + top;

View File

@ -13,11 +13,11 @@ import 'package:flutter_card_swiper/src/undoable.dart';
class CardSwiper extends StatefulWidget { class CardSwiper extends StatefulWidget {
/// Function that builds each card in the stack. /// Function that builds each card in the stack.
/// ///
/// The [int] parameter specifies the index of the card to build, and the [BuildContext] /// The function is called with the index of the card to be built, the build context, the ratio
/// parameter provides the build context. The function should return a widget that represents /// of vertical drag to [threshold] as a percentage, and the ratio of horizontal drag to [threshold]
/// the card at the given index. It can return `null`, which will result in an /// as a percentage. The function should return a widget that represents the card at the given index.
/// empty card being displayed. /// It can return `null`, which will result in an empty card being displayed.
final NullableIndexedWidgetBuilder cardBuilder; final NullableCardBuilder cardBuilder;
/// The number of cards in the stack. /// The number of cards in the stack.
/// ///
@ -111,6 +111,11 @@ class CardSwiper extends StatefulWidget {
/// on top of the stack. If the function returns `true`, the undo action is performed as expected. /// on top of the stack. If the function returns `true`, the undo action is performed as expected.
final CardSwiperOnUndo? onUndo; final CardSwiperOnUndo? onUndo;
/// Callback function that is called when a card swipe direction changes.
///
/// The function is called with the last detected horizontal direction and the last detected vertical direction
final CardSwiperDirectionChange? onSwipeDirectionChange;
/// The offset of the back card from the front card. /// The offset of the back card from the front card.
/// ///
/// In order to keep the back card position same after changing the [backCardOffset], /// In order to keep the back card position same after changing the [backCardOffset],
@ -137,6 +142,7 @@ class CardSwiper extends StatefulWidget {
this.onSwipe, this.onSwipe,
this.onEnd, this.onEnd,
this.direction = CardSwiperDirection.right, this.direction = CardSwiperDirection.right,
this.onSwipeDirectionChange,
this.allowedSwipeDirection = const AllowedSwipeDirection.all(), this.allowedSwipeDirection = const AllowedSwipeDirection.all(),
this.isLoop = true, this.isLoop = true,
this.numberOfCardsDisplayed = 2, this.numberOfCardsDisplayed = 2,
@ -179,6 +185,8 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
SwipeType _swipeType = SwipeType.none; SwipeType _swipeType = SwipeType.none;
CardSwiperDirection _detectedDirection = CardSwiperDirection.none; CardSwiperDirection _detectedDirection = CardSwiperDirection.none;
CardSwiperDirection _detectedHorizontalDirection = CardSwiperDirection.none;
CardSwiperDirection _detectedVerticalDirection = CardSwiperDirection.none;
bool _tappedOnTop = false; bool _tappedOnTop = false;
final _undoableIndex = Undoable<int?>(null); final _undoableIndex = Undoable<int?>(null);
@ -211,9 +219,33 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
initialScale: widget.scale, initialScale: widget.scale,
allowedSwipeDirection: widget.allowedSwipeDirection, allowedSwipeDirection: widget.allowedSwipeDirection,
initialOffset: widget.backCardOffset, initialOffset: widget.backCardOffset,
onSwipeDirectionChanged: onSwipeDirectionChanged,
); );
} }
void onSwipeDirectionChanged(CardSwiperDirection direction) {
if (direction == CardSwiperDirection.none) {
_detectedVerticalDirection = direction;
_detectedHorizontalDirection = direction;
widget.onSwipeDirectionChange
?.call(_detectedHorizontalDirection, _detectedVerticalDirection);
} else if (direction == CardSwiperDirection.right ||
direction == CardSwiperDirection.left) {
if (_detectedHorizontalDirection != direction) {
_detectedHorizontalDirection = direction;
widget.onSwipeDirectionChange
?.call(_detectedHorizontalDirection, _detectedVerticalDirection);
}
} else if (direction == CardSwiperDirection.top ||
direction == CardSwiperDirection.bottom) {
if (_detectedVerticalDirection != direction) {
_detectedVerticalDirection = direction;
widget.onSwipeDirectionChange
?.call(_detectedHorizontalDirection, _detectedVerticalDirection);
}
}
}
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
@ -234,7 +266,6 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
fit: StackFit.expand, fit: StackFit.expand,
children: List.generate(numberOfCardsOnScreen(), (index) { children: List.generate(numberOfCardsOnScreen(), (index) {
if (index == 0) return _frontItem(constraints); if (index == 0) return _frontItem(constraints);
return _backItem(constraints, index); return _backItem(constraints, index);
}).reversed.toList(), }).reversed.toList(),
); );
@ -254,7 +285,12 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
angle: _cardAnimation.angle, angle: _cardAnimation.angle,
child: ConstrainedBox( child: ConstrainedBox(
constraints: constraints, constraints: constraints,
child: widget.cardBuilder(context, _currentIndex!), child: widget.cardBuilder(
context,
_currentIndex!,
(100 * _cardAnimation.left / widget.threshold).ceil(),
(100 * _cardAnimation.top / widget.threshold).ceil(),
),
), ),
), ),
onTap: () async { onTap: () async {
@ -299,7 +335,7 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
scale: _cardAnimation.scale - ((1 - widget.scale) * (index - 1)), scale: _cardAnimation.scale - ((1 - widget.scale) * (index - 1)),
child: ConstrainedBox( child: ConstrainedBox(
constraints: constraints, constraints: constraints,
child: widget.cardBuilder(context, getValidIndexOffset(index)!), child: widget.cardBuilder(context, getValidIndexOffset(index)!, 0, 0),
), ),
), ),
); );
@ -363,6 +399,8 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
} }
void _reset() { void _reset() {
onSwipeDirectionChanged(CardSwiperDirection.none);
_detectedDirection = CardSwiperDirection.none;
setState(() { setState(() {
_animationController.reset(); _animationController.reset();
_cardAnimation.reset(); _cardAnimation.reset();
@ -402,7 +440,6 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
void _swipe(CardSwiperDirection direction) { void _swipe(CardSwiperDirection direction) {
if (_currentIndex == null) return; if (_currentIndex == null) return;
_swipeType = SwipeType.swipe; _swipeType = SwipeType.swipe;
_detectedDirection = direction; _detectedDirection = direction;
_cardAnimation.animate(context, direction); _cardAnimation.animate(context, direction);
@ -410,7 +447,6 @@ class _CardSwiperState<T extends Widget> extends State<CardSwiper>
void _goBack() { void _goBack() {
_swipeType = SwipeType.back; _swipeType = SwipeType.back;
_detectedDirection = CardSwiperDirection.none;
_cardAnimation.animateBack(context); _cardAnimation.animateBack(context);
} }

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_card_swiper/src/enums.dart'; import 'package:flutter_card_swiper/src/enums.dart';
typedef CardSwiperOnSwipe = FutureOr<bool> Function( typedef CardSwiperOnSwipe = FutureOr<bool> Function(
@ -8,6 +9,23 @@ typedef CardSwiperOnSwipe = FutureOr<bool> Function(
CardSwiperDirection direction, CardSwiperDirection direction,
); );
typedef CardSwiperOnSwipeUpdate = Function(
int? currentIndex,
CardSwiperDirection direction,
);
typedef NullableCardBuilder = Widget? Function(
BuildContext context,
int index,
int horizontalOffsetPercentage,
int verticalOffsetPercentage,
);
typedef CardSwiperDirectionChange = Function(
CardSwiperDirection horizontalDirection,
CardSwiperDirection verticalDirection,
);
typedef CardSwiperOnEnd = FutureOr<void> Function(); typedef CardSwiperOnEnd = FutureOr<void> Function();
typedef CardSwiperOnTapDisabled = FutureOr<void> Function(); typedef CardSwiperOnTapDisabled = FutureOr<void> Function();

View File

@ -2,7 +2,7 @@ name: flutter_card_swiper
description: This is a Tinder-like card swiper package. It allows you to swipe left, right, up, and down and define your own business logic for each direction. description: This is a Tinder-like card swiper package. It allows you to swipe left, right, up, and down and define your own business logic for each direction.
homepage: https://github.com/ricardodalarme/flutter_card_swiper homepage: https://github.com/ricardodalarme/flutter_card_swiper
issue_tracker: https://github.com/ricardodalarme/flutter_card_swiper/issues issue_tracker: https://github.com/ricardodalarme/flutter_card_swiper/issues
version: 5.1.0 version: 6.0.0
environment: environment:
sdk: ">=2.12.0 <4.0.0" sdk: ">=2.12.0 <4.0.0"

View File

@ -1,6 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
Widget? genericBuilder(BuildContext context, int index) { Widget? genericBuilder(
BuildContext context,
int index,
int horizontalPercentage,
int verticalPercentage,
) {
return Container( return Container(
width: 200, width: 200,
height: 200, height: 200,