2022-11-01 08:20:01 +01:00
|
|
|
// SPDX-FileCopyrightText: 2022 Iconica
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
2022-09-02 14:00:16 +02:00
|
|
|
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.
|
2024-02-02 10:39:41 +01:00
|
|
|
/// Each card can change its rotation, position
|
|
|
|
/// and scale when swiping the cards.
|
2022-09-02 14:00:16 +02:00
|
|
|
/// 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,
|
2023-09-26 09:53:56 +02:00
|
|
|
this.initialPage = 0,
|
|
|
|
this.allowInfiniteScrollingBackwards = false,
|
2024-02-02 10:39:41 +01:00
|
|
|
super.key,
|
|
|
|
});
|
2022-09-02 14:00:16 +02:00
|
|
|
|
|
|
|
/// 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<CardTransform> transforms;
|
|
|
|
|
|
|
|
/// The index of the transform card which acts as the selected card.
|
|
|
|
final int selectableCardId;
|
|
|
|
|
2024-02-02 10:39:41 +01:00
|
|
|
/// Builder for the card given a [context] and a [index] to
|
|
|
|
/// identify the right card.
|
2022-09-02 14:00:16 +02:00
|
|
|
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;
|
|
|
|
|
2023-09-26 09:53:56 +02:00
|
|
|
/// The page to show when first creating the [Carousel].
|
|
|
|
final int initialPage;
|
|
|
|
|
2024-02-02 10:39:41 +01:00
|
|
|
/// Whether to allow infinite scrolling backwards. Defaults to false. If true,
|
|
|
|
/// this works by using a very large number of pages (10000).
|
|
|
|
/// Works in conjunction with [initialPage].
|
2023-09-26 09:53:56 +02:00
|
|
|
final bool allowInfiniteScrollingBackwards;
|
|
|
|
|
2022-09-02 14:00:16 +02:00
|
|
|
@override
|
|
|
|
State<Carousel> createState() => _CarouselState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _CarouselState extends State<Carousel> {
|
2023-09-26 09:53:56 +02:00
|
|
|
late PageController _pageController;
|
2022-09-02 14:00:16 +02:00
|
|
|
double _currentPage = 0;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
2023-09-26 09:53:56 +02:00
|
|
|
_pageController = PageController(
|
|
|
|
initialPage: widget.allowInfiniteScrollingBackwards
|
|
|
|
? 10000 + widget.initialPage
|
|
|
|
: widget.initialPage,
|
|
|
|
);
|
|
|
|
_currentPage = _pageController.initialPage.toDouble();
|
|
|
|
|
2022-09-02 14:00:16 +02:00
|
|
|
_pageController.addListener(() {
|
|
|
|
_currentPage = _pageController.page!;
|
|
|
|
});
|
|
|
|
super.initState();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_pageController.dispose();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2024-02-02 10:39:41 +01:00
|
|
|
Widget build(BuildContext context) => Stack(
|
|
|
|
alignment: widget.alignment,
|
|
|
|
children: [
|
|
|
|
AnimatedBuilder(
|
|
|
|
animation: _pageController,
|
|
|
|
builder: (context, _) {
|
|
|
|
var transitionPos = _currentPage % 1;
|
|
|
|
var index = _currentPage.floor();
|
|
|
|
var 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),
|
|
|
|
),
|
|
|
|
],
|
2022-09-02 14:00:16 +02:00
|
|
|
],
|
2024-02-02 10:39:41 +01:00
|
|
|
);
|
|
|
|
},
|
|
|
|
),
|
|
|
|
SizedBox(
|
|
|
|
height: widget.pageViewHeight,
|
|
|
|
child: PageView.builder(
|
|
|
|
scrollBehavior: _MouseSwipeOnWeb(),
|
|
|
|
onPageChanged: widget.onPageChanged,
|
|
|
|
controller: _pageController,
|
|
|
|
itemBuilder: (context, index) => Visibility(
|
2022-09-02 14:00:16 +02:00
|
|
|
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),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
2024-02-02 10:39:41 +01:00
|
|
|
),
|
|
|
|
),
|
2022-09-02 14:00:16 +02:00
|
|
|
),
|
2024-02-02 10:39:41 +01:00
|
|
|
],
|
|
|
|
);
|
2022-09-02 14:00:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class _MouseSwipeOnWeb extends MaterialScrollBehavior {
|
|
|
|
@override
|
|
|
|
Set<PointerDeviceKind> get dragDevices => {
|
|
|
|
PointerDeviceKind.touch,
|
|
|
|
PointerDeviceKind.mouse,
|
|
|
|
};
|
|
|
|
}
|