diff --git a/README.md b/README.md index 572d855..9142298 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,27 @@ -# carousel -card carousel widget +# Carousel +Carousel widget. Makes it easier to create card carousels using a list of transforms. +Each card can change its rotation, position and scale when swiping the cards. + +Supports all platforms. + +![Demo video](demo.gif) + +## Usage + +To use this package, add `carousel` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). + +### Example + +See [Example Code](example/lib/main.dart) for more info. + +## Issues + +Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/carousel) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl). + +## Want to contribute + +If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](./CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/carousel/pulls). + +## Author + +This carousel for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at \ No newline at end of file diff --git a/demo.gif b/demo.gif new file mode 100644 index 0000000..5205c4b Binary files /dev/null and b/demo.gif differ diff --git a/example/lib/main.dart b/example/lib/main.dart index 148db2b..8527dc8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,5 +1,5 @@ import 'package:carousel/carousel.dart'; -import 'package:carousel/models/card_transform.dart'; + import 'package:carousel_example/pokemon.dart'; import 'package:carousel_example/pokemon_card.dart'; import 'package:flutter/material.dart'; diff --git a/lib/carousel.dart b/lib/carousel.dart index 03f1b4e..c706e69 100644 --- a/lib/carousel.dart +++ b/lib/carousel.dart @@ -1,125 +1,4 @@ library carousel; -export 'package:carousel/carousel.dart'; - -import 'package:carousel/models/card_transform.dart'; -import 'package:carousel/widgets/carousel_card.dart'; -import 'package:flutter/material.dart'; - -typedef CarouselCardBuilder = Widget Function(BuildContext context, int index); - -class Carousel extends StatefulWidget { - const Carousel({ - required this.transforms, - required this.builder, - required this.selectableCardId, - this.pageViewHeight = 300, - this.onPageChanged, - this.alignment = AlignmentDirectional.topStart, - this.onCardClick, - Key? key, - }) : super(key: key); - - /// A list of transforms to calculate the position of the card when swiping. - /// Every item in the list is one of the possible card positions. - final List transforms; - - /// The index of the transform card which acts as the selected card. - final int selectableCardId; - - /// Builder for the card given a [context] and a [index] to identify the right card. - final CarouselCardBuilder builder; - - /// Called when selected card is changed to the next one. - final void Function(int value)? onPageChanged; - - // Callen when selected card is clicked. - final void Function(int value)? onCardClick; - - /// Alignment of the cards. - final AlignmentGeometry alignment; - - /// Size of the pageview used to capture swipe gestures. - final double pageViewHeight; - - @override - State createState() => _CarouselState(); -} - -class _CarouselState extends State { - final PageController _pageController = PageController(initialPage: 0); - double _currentPage = 0; - - @override - void initState() { - _pageController.addListener(() { - _currentPage = _pageController.page!; - }); - super.initState(); - } - - @override - void dispose() { - _pageController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Stack( - alignment: widget.alignment, - children: [ - AnimatedBuilder( - animation: _pageController, - builder: (context, _) { - final transitionPos = _currentPage % 1; - final index = _currentPage.floor(); - final length = widget.transforms.length - 1; - - return Stack( - children: [ - for (var i = 0; i < length; i++) ...[ - CarouselCard( - cardTransform: widget.transforms[i] - .transform(widget.transforms[i + 1], transitionPos), - child: widget.builder.call(context, index - i), - ), - ], - ], - ); - }, - ), - SizedBox( - height: widget.pageViewHeight, - child: PageView.builder( - onPageChanged: widget.onPageChanged, - controller: _pageController, - itemBuilder: (context, index) { - return Visibility( - visible: false, - maintainState: true, - maintainAnimation: true, - maintainInteractivity: true, - maintainSemantics: true, - maintainSize: true, - child: Stack( - alignment: widget.alignment, - children: [ - GestureDetector( - onTap: () { - widget.onCardClick - ?.call(index - widget.selectableCardId); - }, - child: widget.builder - .call(context, index - widget.selectableCardId), - ), - ], - ), - ); - }, - ), - ), - ], - ); - } -} +export 'package:carousel/src/carousel.dart'; +export 'package:carousel/src/models/card_transform.dart'; diff --git a/lib/src/carousel.dart b/lib/src/carousel.dart new file mode 100644 index 0000000..e791fbf --- /dev/null +++ b/lib/src/carousel.dart @@ -0,0 +1,134 @@ +import 'package:carousel/src/models/card_transform.dart'; +import 'package:carousel/src/widgets/carousel_card.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +typedef CarouselCardBuilder = Widget Function(BuildContext context, int index); + +class Carousel extends StatefulWidget { + /// Animated cards by swiping. + /// Each card can change its rotation, position and scale when swiping the cards. + /// Transform path can be privided using [transforms] + const Carousel({ + required this.transforms, + required this.builder, + required this.selectableCardId, + this.pageViewHeight = 300, + this.onPageChanged, + this.alignment = AlignmentDirectional.topStart, + this.onCardClick, + Key? key, + }) : super(key: key); + + /// A list of transforms to calculate the position of the card when swiping. + /// Every item in the list is one of the possible card positions. + final List transforms; + + /// The index of the transform card which acts as the selected card. + final int selectableCardId; + + /// Builder for the card given a [context] and a [index] to identify the right card. + final CarouselCardBuilder builder; + + /// Called when selected card is changed to the next one. + final void Function(int value)? onPageChanged; + + // Callen when selected card is clicked. + final void Function(int value)? onCardClick; + + /// Alignment of the cards. + final AlignmentGeometry alignment; + + /// Size of the pageview used to capture swipe gestures. + final double pageViewHeight; + + @override + State createState() => _CarouselState(); +} + +class _CarouselState extends State { + final PageController _pageController = PageController(initialPage: 0); + double _currentPage = 0; + + @override + void initState() { + _pageController.addListener(() { + _currentPage = _pageController.page!; + }); + super.initState(); + } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + alignment: widget.alignment, + children: [ + AnimatedBuilder( + animation: _pageController, + builder: (context, _) { + final transitionPos = _currentPage % 1; + final index = _currentPage.floor(); + final length = widget.transforms.length - 1; + + return Stack( + children: [ + for (var i = 0; i < length; i++) ...[ + CarouselCard( + cardTransform: widget.transforms[i] + .transform(widget.transforms[i + 1], transitionPos), + child: widget.builder.call(context, index - i), + ), + ], + ], + ); + }, + ), + SizedBox( + height: widget.pageViewHeight, + child: PageView.builder( + scrollBehavior: _MouseSwipeOnWeb(), + onPageChanged: widget.onPageChanged, + controller: _pageController, + itemBuilder: (context, index) { + return Visibility( + visible: false, + maintainState: true, + maintainAnimation: true, + maintainInteractivity: true, + maintainSemantics: true, + maintainSize: true, + child: Stack( + alignment: widget.alignment, + children: [ + GestureDetector( + onTap: () { + widget.onCardClick + ?.call(index - widget.selectableCardId); + }, + child: widget.builder + .call(context, index - widget.selectableCardId), + ), + ], + ), + ); + }, + ), + ), + ], + ); + } +} + +class _MouseSwipeOnWeb extends MaterialScrollBehavior { + @override + Set get dragDevices => { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + }; +} diff --git a/lib/models/card_transform.dart b/lib/src/models/card_transform.dart similarity index 72% rename from lib/models/card_transform.dart rename to lib/src/models/card_transform.dart index 5370d8c..95ee728 100644 --- a/lib/models/card_transform.dart +++ b/lib/src/models/card_transform.dart @@ -1,4 +1,5 @@ class CardTransform { + /// Used by [Carousel] to build cards on the correct position. CardTransform({ this.x = 0, this.y = 0, @@ -10,6 +11,9 @@ class CardTransform { double angle; double scale; + /// [transitionPos] is a position value of a swipe for example. + /// [other] is the position, scale, rotation + /// which the current [CardTransform] need to be transformed to. CardTransform transform(CardTransform other, double transitionPos) { return CardTransform( x: _transformValue(x, other.x, transitionPos), diff --git a/lib/widgets/carousel_card.dart b/lib/src/widgets/carousel_card.dart similarity index 85% rename from lib/widgets/carousel_card.dart rename to lib/src/widgets/carousel_card.dart index d5d2455..97285e4 100644 --- a/lib/widgets/carousel_card.dart +++ b/lib/src/widgets/carousel_card.dart @@ -1,6 +1,7 @@ -import 'package:carousel/models/card_transform.dart'; +import 'package:carousel/src/models/card_transform.dart'; import 'package:flutter/material.dart'; +/// Transformed card used in [Carousel] class CarouselCard extends StatelessWidget { const CarouselCard({ required this.cardTransform, diff --git a/test/carousel_test.dart b/test/carousel_test.dart index 5b2cb0c..771831c 100644 --- a/test/carousel_test.dart +++ b/test/carousel_test.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:carousel/models/card_transform.dart'; +import 'package:carousel/src/models/card_transform.dart'; import 'package:flutter_test/flutter_test.dart'; void main() {