Skip to content

Instantly share code, notes, and snippets.

@Origogi
Last active August 3, 2021 06:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Origogi/5eaa69dfeba45bdc3d05b715d16ddc5f to your computer and use it in GitHub Desktop.
Save Origogi/5eaa69dfeba45bdc3d05b715d16ddc5f to your computer and use it in GitHub Desktop.
flutter card pager sample
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
typedef PageChangedCallback = void Function(double page);
typedef PageSelectedCallback = void Function(int index);
enum ALIGN { LEFT, CENTER, RIGHT }
class VerticalCardPager extends StatefulWidget {
final List<String> titles;
final List<Widget> images;
final PageChangedCallback onPageChanged;
final PageSelectedCallback onSelectedItem;
final TextStyle textStyle;
final int initialPage;
final ALIGN align;
VerticalCardPager(
{required this.titles,
required this.images,
required this.onPageChanged,
required this.textStyle,
this.initialPage = 2,
required this.onSelectedItem,
this.align = ALIGN.CENTER})
: assert(titles.length == images.length);
@override
_VerticalCardPagerState createState() => _VerticalCardPagerState();
}
class _VerticalCardPagerState extends State<VerticalCardPager> {
bool isScrolling = false;
double currentPosition = 0;
late PageController controller;
@override
void initState() {
super.initState();
currentPosition = widget.initialPage.toDouble();
controller = PageController(initialPage: widget.initialPage);
controller.addListener(() {
setState(() {
currentPosition = controller.page!;
Future(() => widget.onPageChanged(currentPosition));
});
});
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return GestureDetector(
onVerticalDragEnd: (details) {
isScrolling = false;
},
onVerticalDragStart: (details) {
isScrolling = true;
},
onTapUp: (details) {
if ((currentPosition - currentPosition.floor()).abs() <= 0.15) {
int selectedIndex = onTapUp(
context, constraints.maxHeight, constraints.maxWidth, details);
if (selectedIndex == 2) {
Future(() => widget.onSelectedItem(currentPosition.round()));
} else if (selectedIndex >= 0) {
int goToPage = currentPosition.toInt() + selectedIndex - 2;
controller.animateToPage(goToPage,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOutExpo);
}
}
},
child: Stack(
children: [
CardControllerWidget(
titles: widget.titles,
images: widget.images,
textStyle: widget.textStyle,
currentPostion: currentPosition,
cardViewPagerHeight: constraints.maxHeight,
cardViewPagerWidth: constraints.maxWidth,
align: widget.align,
),
Positioned.fill(
child: PageView.builder(
scrollDirection: Axis.vertical,
itemCount: widget.images.length,
controller: controller,
itemBuilder: (context, index) {
return Container();
},
),
)
],
),
);
});
}
int onTapUp(context, maxHeight, maxWidth, details) {
final RenderBox box = context.findRenderObject();
final Offset localOffset = box.globalToLocal(details.globalPosition);
double dx = localOffset.dx;
double dy = localOffset.dy;
for (int i = 0; i < 5; i++) {
double width = getWidth(maxHeight, i);
double height = getHeight(maxHeight, i);
double left = getStartPositon(maxWidth, width);
double top = getCardPositionTop(height, maxHeight, i);
if (top <= dy && dy <= top + height) {
if (left <= dx && dx <= left + width) {
return i;
}
}
}
return -1;
}
double getStartPositon(cardViewPagerWidth, cardWidth) {
double position = 0;
switch (widget.align) {
case ALIGN.LEFT:
position = 0;
break;
case ALIGN.CENTER:
position = (cardViewPagerWidth / 2) - (cardWidth / 2);
break;
case ALIGN.RIGHT:
position = cardViewPagerWidth - cardWidth;
break;
}
return position;
}
double getWidth(maxHeight, i) {
double cardMaxWidth = maxHeight / 2;
return cardMaxWidth - 60 * (i - 2).abs();
}
double getHeight(maxHeight, i) {
double cardMaxHeight = maxHeight / 2;
if (i == 2) {
return cardMaxHeight;
} else if (i == 0 || i == 4) {
return cardMaxHeight - cardMaxHeight * (4 / 5) - 10;
} else
return cardMaxHeight - cardMaxHeight * (4 / 5);
}
}
double getCardPositionTop(double cardHeight, double viewHeight, int i) {
int diff = (2 - i);
int diffAbs = diff.abs();
double basePosition = (viewHeight / 2) - (cardHeight / 2);
double cardMaxHeight = viewHeight / 2;
if (diffAbs == 0) {
return basePosition;
}
if (diffAbs == 1) {
if (diff >= 0) {
return basePosition - (cardMaxHeight * (6 / 9));
} else {
return basePosition + (cardMaxHeight * (6 / 9));
}
} else {
if (diff >= 0) {
return basePosition - cardMaxHeight * (8 / 9);
} else {
return basePosition + cardMaxHeight * (8 / 9);
}
}
}
class CardControllerWidget extends StatelessWidget {
final double currentPostion;
final double cardMaxWidth;
final double cardMaxHeight;
final double cardViewPagerHeight;
final double cardViewPagerWidth;
final TextStyle textStyle;
final ALIGN align;
final List titles;
final List images;
CardControllerWidget(
{required this.titles,
required this.images,
required this.cardViewPagerWidth,
required this.cardViewPagerHeight,
required this.currentPostion,
required this.align,
required this.textStyle})
: cardMaxHeight = cardViewPagerHeight * (1 / 2),
cardMaxWidth = cardViewPagerHeight * (1 / 2);
@override
Widget build(BuildContext context) {
List<Widget> cardList = [];
final titleTextStyle = textStyle;
for (int i = 0; i < images.length; i++) {
var cardWidth = max(cardMaxWidth - 60 * (currentPostion - i).abs(), 0.0);
var cardHeight = getCardHeight(i);
var cardTop = getTop(cardHeight, cardViewPagerHeight, i);
Widget card = Positioned.directional(
textDirection: TextDirection.ltr,
top: cardTop,
start: getStartPositon(cardWidth),
child: Opacity(
opacity: getOpacity(i),
child: Container(
width: cardWidth,
height: cardHeight,
child: Stack(
children: <Widget>[
Positioned.fill(
child: images[i],
),
Align(
child: Text(
titles[i],
style: titleTextStyle.copyWith(fontSize: getFontSize(i)),
textAlign: TextAlign.center,
)),
],
),
),
),
);
cardList.add(card);
}
return Stack(
children: cardList,
);
}
double getOpacity(int i) {
double diff = (currentPostion - i);
if (diff >= -2 && diff <= 2) {
return 1.0;
} else if (diff > -3 && diff < -2) {
return 3 - diff.abs();
} else if (diff > 2 && diff < 3) {
return 3 - diff.abs();
} else {
return 0;
}
}
double getTop(double cardHeight, double viewHeight, int i) {
double diff = (currentPostion - i);
double diffAbs = diff.abs();
double basePosition = (viewHeight / 2) - (cardHeight / 2);
if (diffAbs == 0) {
return basePosition;
}
if (diffAbs > 0.0 && diffAbs <= 1.0) {
if (diff >= 0) {
return basePosition - (cardMaxHeight * (6 / 9)) * diffAbs;
} else {
return basePosition + (cardMaxHeight * (6 / 9)) * diffAbs;
}
} else if (diffAbs > 1.0 && diffAbs < 2.0) {
if (diff >= 0) {
return basePosition -
(cardMaxHeight * (6 / 9)) -
cardMaxHeight * (2 / 9) * (diffAbs - diffAbs.floor()).abs();
} else {
return basePosition +
(cardMaxHeight * (6 / 9)) +
cardMaxHeight * (2 / 9) * (diffAbs - diffAbs.floor()).abs();
}
} else {
if (diff >= 0) {
return basePosition - cardMaxHeight * (8 / 9);
} else {
return basePosition + cardMaxHeight * (8 / 9);
}
}
}
double getCardHeight(int index) {
double diff = (currentPostion - index).abs();
if (diff >= 0.0 && diff < 1.0) {
return cardMaxHeight - cardMaxHeight * (4 / 5) * ((diff - diff.floor()));
} else if (diff >= 1.0 && diff < 2.0) {
return cardMaxHeight -
cardMaxHeight * (4 / 5) -
10 * ((diff - diff.floor()));
} else {
final height = cardMaxHeight -
cardMaxHeight * (4 / 5) -
10 -
5 * ((diff - diff.floor()));
return height > 0 ? height : 0;
}
}
double getFontSize(int index) {
double diffAbs = (currentPostion - index).abs();
diffAbs = num.parse(diffAbs.toStringAsFixed(2)).toDouble();
double maxFontSize = 50;
if (diffAbs >= 0.0 && diffAbs < 1.0) {
if (diffAbs < 0.02) {
diffAbs = 0;
}
return maxFontSize - 25 * ((diffAbs - diffAbs.floor()));
} else if (diffAbs >= 1.0 && diffAbs < 2.0) {
return maxFontSize - 25 - 5 * ((diffAbs - diffAbs.floor()));
} else {
final fontSize = maxFontSize - 30 - 15 * ((diffAbs - diffAbs.floor()));
return fontSize > 0 ? fontSize : 0;
}
}
double getStartPositon(cardWidth) {
double position = 0;
switch (align) {
case ALIGN.LEFT:
position = 0;
break;
case ALIGN.CENTER:
position = (cardViewPagerWidth / 2) - (cardWidth / 2);
break;
case ALIGN.RIGHT:
position = cardViewPagerWidth - cardWidth;
break;
}
return position;
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
final List<String> titles = [
"AKALI",
"CAMILE",
"EZREAL",
"IRELIA",
"POPPY",
"ZOE",
];
final List<Widget> images = [
Hero(
tag: "AKALI",
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Image.network(
"https://i.imgur.com/qBNHFMh.gif",
fit: BoxFit.cover,
),
),
),
Hero(
tag: "CAMILE",
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Image.network(
"https://i.imgur.com/KDix8a8.gif",
fit: BoxFit.cover,
),
),
),
Hero(
tag: "EZREAL",
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Image.network(
"https://i.imgur.com/TiHbX32.gif",
fit: BoxFit.cover,
),
),
),
Hero(
tag: "IRELIA",
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Image.network(
"https://i.imgur.com/qVr96DF.gif",
fit: BoxFit.cover,
),
),
),
Hero(
tag: "POPPY",
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Image.network(
"https://i.imgur.com/Q4SlrHT.gif",
fit: BoxFit.cover,
),
),
),
Hero(
tag: "ZOE",
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Image.network(
"https://i.imgur.com/hwCsKMp.gif",
fit: BoxFit.cover,
),
),
),
];
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
Container(
width: double.infinity,
height: 70,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 15.0),
child: Center(
child: Image.network(
"https://i.imgur.com/dr8KXRi.png",
fit: BoxFit.cover,
)),
),
),
Expanded(
child: Container(
child: VerticalCardPager(
textStyle: TextStyle(color : Colors.white, fontStyle: FontStyle.italic),
titles: titles,
images: images,
onPageChanged: (page) {
// print(page);
},
onSelectedItem: (index) {
},
),
),
),
],
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment