├── .gitignore ├── .metadata ├── README.md ├── android.iml ├── assets └── images │ ├── img1.jpeg │ ├── img2.jpeg │ ├── img3.jpeg │ ├── img4.jpeg │ ├── img5.jpeg │ └── img6.jpeg ├── carousel_cards.iml ├── lib ├── card_info.dart ├── carousel_body.dart ├── carousel_screen.dart └── main.dart ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .atom/ 3 | .idea 4 | .vscode/ 5 | .packages 6 | .pub/ 7 | build/ 8 | ios/.generated/ 9 | packages 10 | pubspec.lock 11 | .flutter-plugins 12 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2e449f06f0a3be076e336ad6b30b0e9ec99dbdfe 8 | channel: alpha 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overlapping Cards with Carousel Effect 2 | 3 | # Screencast 4 | ![Screenshot](https://media.giphy.com/media/4T7zQzeBexWyyXMpfa/giphy.gif) 5 | -------------------------------------------------------------------------------- /android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /assets/images/img1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoojaB26/CarouselOverlappingCards-Flutter/e1587bf9fc28622d94c93c414efc4871ead032ee/assets/images/img1.jpeg -------------------------------------------------------------------------------- /assets/images/img2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoojaB26/CarouselOverlappingCards-Flutter/e1587bf9fc28622d94c93c414efc4871ead032ee/assets/images/img2.jpeg -------------------------------------------------------------------------------- /assets/images/img3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoojaB26/CarouselOverlappingCards-Flutter/e1587bf9fc28622d94c93c414efc4871ead032ee/assets/images/img3.jpeg -------------------------------------------------------------------------------- /assets/images/img4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoojaB26/CarouselOverlappingCards-Flutter/e1587bf9fc28622d94c93c414efc4871ead032ee/assets/images/img4.jpeg -------------------------------------------------------------------------------- /assets/images/img5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoojaB26/CarouselOverlappingCards-Flutter/e1587bf9fc28622d94c93c414efc4871ead032ee/assets/images/img5.jpeg -------------------------------------------------------------------------------- /assets/images/img6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoojaB26/CarouselOverlappingCards-Flutter/e1587bf9fc28622d94c93c414efc4871ead032ee/assets/images/img6.jpeg -------------------------------------------------------------------------------- /carousel_cards.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/card_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | final category = new Row( 4 | children: [ 5 | Container( 6 | width: 3.0, 7 | height: 12.0, 8 | color: Colors.redAccent, 9 | ), 10 | SizedBox(width: 10.0,), 11 | new Text("International Offer", 12 | style: new TextStyle(fontSize: 11.0), 13 | ) 14 | ], 15 | ); 16 | 17 | final titleStyle = new TextStyle( 18 | color: Colors.black, 19 | fontSize: 18.0, 20 | fontWeight: FontWeight.w700 21 | ); 22 | final title = new Container( 23 | padding: new EdgeInsets.only(left: 15.0), 24 | child: new Text("Introducing myBiz", style: titleStyle,), 25 | ); 26 | 27 | final subTitleStyle = new TextStyle( 28 | color: Colors.black, 29 | fontSize: 14.0, 30 | fontWeight: FontWeight.w300 31 | ); 32 | final subTitle = new Container( 33 | padding: new EdgeInsets.only(left: 15.0, right: 15.0), 34 | child: new Text("A smart corporate tool for business travelers. \n Flat 10,000 benefits on for First Booking", style: subTitleStyle, 35 | maxLines: 3,), 36 | ); -------------------------------------------------------------------------------- /lib/carousel_body.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:carousel_cards/card_info.dart'; 3 | 4 | const double _mViewportFraction = 0.9; 5 | 6 | class CarouselBody extends StatefulWidget { 7 | CarouselBody({Key key}) : super(key: key); 8 | 9 | @override 10 | _CarouselBodyState createState() => _CarouselBodyState(); 11 | } 12 | 13 | class _CarouselBodyState extends State { 14 | final PageController _backgroundController = PageController(viewportFraction: _mViewportFraction); 15 | final PageController _cardController = 16 | PageController(viewportFraction: _mViewportFraction); 17 | ValueNotifier selectedIndex = ValueNotifier(0.0); 18 | 19 | bool _handleNotification(ScrollNotification notification, 20 | PageController leader, PageController follower) { 21 | if (notification.depth == 0 && notification is ScrollUpdateNotification) { 22 | selectedIndex.value = leader.page; 23 | if (follower.page != leader.page) { 24 | //follower.animateToPage(leader.page.toInt(), duration: Duration(milliseconds: 10000), curve: Curves.easeOut); 25 | follower.position.jumpToWithoutSettling(leader.position.pixels);// ignore: deprecated_member_use 26 | //return true --> makes the card also take uneven space like the bg 27 | } 28 | setState(() {}); 29 | } 30 | return false; 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Stack( 36 | children: [ 37 | /*PageView( 38 | controller: _backgroundController, 39 | children: _buildBackgroundImages(), 40 | ),*/ 41 | NotificationListener( 42 | onNotification: (ScrollNotification notification) { 43 | return _handleNotification( 44 | notification, _cardController, _backgroundController); 45 | }, 46 | child: PageView( 47 | controller: _backgroundController, 48 | children: _buildBackgroundImages(), 49 | ), 50 | ), 51 | NotificationListener( 52 | onNotification: (ScrollNotification notification) { 53 | return _handleNotification( 54 | notification, _cardController, _backgroundController); 55 | }, 56 | child: PageView( 57 | controller: _cardController, 58 | children: _buildCards(), 59 | ), 60 | ), 61 | ], 62 | ); 63 | } 64 | 65 | Iterable _buildBackgroundImages() { 66 | final List backgroundPages = []; 67 | 68 | double bgHeight = MediaQuery.of(context).size.height; 69 | double bgWidth = MediaQuery.of(context).size.width * 0.8; 70 | 71 | 72 | for (int index = 0; index < 6; index++) { 73 | 74 | var alignment = Alignment.center.add( 75 | Alignment((selectedIndex.value - index), 0.0)); 76 | var resizeFactor = 77 | (1 - (((selectedIndex.value - index).abs() * 0.4).clamp(0.0, 1.0))); 78 | 79 | var imageAsset = 'assets/images/img${index + 1}.jpeg'; 80 | 81 | 82 | backgroundPages.add(SizedBox( 83 | width: bgWidth , 84 | height: bgHeight * resizeFactor, 85 | child: Container( 86 | margin: new EdgeInsets.only(bottom: 80.0), 87 | alignment: alignment, 88 | decoration: new BoxDecoration( 89 | image: new DecorationImage( 90 | image: new AssetImage(imageAsset), 91 | fit: BoxFit.cover, 92 | ), 93 | ), 94 | child: Opacity( 95 | opacity: 0.3, 96 | 97 | ), 98 | ), 99 | )); 100 | } 101 | return backgroundPages; 102 | } 103 | 104 | final roundedCard = new Container( 105 | // child: image, 106 | decoration: new BoxDecoration( 107 | color: Colors.white, 108 | shape: BoxShape.rectangle, 109 | borderRadius: new BorderRadius.circular(8.0), 110 | boxShadow: [ 111 | new BoxShadow( 112 | color: Colors.black12, 113 | blurRadius: 10.0, 114 | offset: new Offset(0.0, 10.0), 115 | ), 116 | ], 117 | ), 118 | 119 | child: new Column( 120 | mainAxisSize: MainAxisSize.max, 121 | mainAxisAlignment: MainAxisAlignment.start, 122 | crossAxisAlignment: CrossAxisAlignment.start, 123 | children: [ 124 | SizedBox(height: 10.0,), 125 | category, 126 | SizedBox(height: 25.0,), 127 | title, 128 | SizedBox(height: 15.0,), 129 | subTitle, 130 | ], 131 | ), 132 | ); 133 | 134 | Iterable _buildCards() { 135 | final List pages = []; 136 | double bgHeight = MediaQuery.of(context).size.height * 0.3; 137 | double bgWidth = MediaQuery.of(context).size.width * 0.8; // changes width of the image 138 | for (int index = 0; index < 6; index++) { 139 | var alignment = Alignment.center.add( 140 | Alignment((selectedIndex.value - index) * _mViewportFraction, 0.0)); 141 | var resizeFactor = 142 | (1 - (((selectedIndex.value - index).abs() * 0.0).clamp(0.0, 1.0))); 143 | pages.add( 144 | Container( 145 | alignment: alignment, 146 | child: new Container( 147 | margin: new EdgeInsets.only(top: 50.0), 148 | width: bgWidth * resizeFactor, 149 | height: bgHeight * resizeFactor, 150 | child: GestureDetector( 151 | onTap: () { 152 | Navigator.of(context).push( 153 | MaterialPageRoute(builder: (context) { 154 | return Hero( 155 | tag: index, 156 | child: null 157 | 158 | ); 159 | })); 160 | }, 161 | child: Hero( 162 | tag: index, 163 | child: roundedCard 164 | ), 165 | ), 166 | ), 167 | ) 168 | ); 169 | } 170 | return pages; 171 | } 172 | } -------------------------------------------------------------------------------- /lib/carousel_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:carousel_cards/carousel_body.dart'; 3 | 4 | class CarouselScreen extends StatelessWidget{ 5 | @override 6 | Widget build(BuildContext context) { 7 | return new Scaffold( 8 | 9 | body: SafeArea( 10 | child: new Column( 11 | mainAxisSize: MainAxisSize.max, 12 | children: [ 13 | 14 | new SizedBox( 15 | height: 250.0, 16 | child: new CarouselBody(), 17 | ) 18 | ], 19 | ), 20 | ), 21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:carousel_cards/carousel_screen.dart'; 3 | 4 | void main() => runApp(MyApp()); 5 | 6 | class MyApp extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return MaterialApp( 10 | debugShowCheckedModeBanner: false, 11 | home: CarouselScreen(), 12 | ); 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: carousel_cards 2 | description: Carousel Cards 3 | 4 | dependencies: 5 | flutter: 6 | sdk: flutter 7 | 8 | # The following adds the Cupertino Icons font to your application. 9 | # Use with the CupertinoIcons class for iOS style icons. 10 | cupertino_icons: ^0.1.0 11 | 12 | dev_dependencies: 13 | flutter_test: 14 | sdk: flutter 15 | 16 | 17 | # For information on the generic Dart part of this file, see the 18 | # following page: https://www.dartlang.org/tools/pub/pubspec 19 | 20 | # The following section is specific to Flutter. 21 | flutter: 22 | 23 | # The following line ensures that the Material Icons font is 24 | # included with your application, so that you can use the icons in 25 | # the material Icons class. 26 | uses-material-design: true 27 | 28 | # To add assets to your application, add an assets section, like this: 29 | assets: 30 | - assets/images/img1.jpeg 31 | - assets/images/img2.jpeg 32 | - assets/images/img3.jpeg 33 | - assets/images/img4.jpeg 34 | - assets/images/img5.jpeg 35 | - assets/images/img6.jpeg 36 | 37 | 38 | # An image asset can refer to one or more resolution-specific "variants", see 39 | # https://flutter.io/assets-and-images/#resolution-aware. 40 | 41 | # For details regarding adding assets from package dependencies, see 42 | # https://flutter.io/assets-and-images/#from-packages 43 | 44 | # To add custom fonts to your application, add a fonts section here, 45 | # in this "flutter" section. Each entry in this list should have a 46 | # "family" key with the font family name, and a "fonts" key with a 47 | # list giving the asset and other descriptors for the font. For 48 | # example: 49 | # fonts: 50 | # - family: Schyler 51 | # fonts: 52 | # - asset: fonts/Schyler-Regular.ttf 53 | # - asset: fonts/Schyler-Italic.ttf 54 | # style: italic 55 | # - family: Trajan Pro 56 | # fonts: 57 | # - asset: fonts/TrajanPro.ttf 58 | # - asset: fonts/TrajanPro_Bold.ttf 59 | # weight: 700 60 | # 61 | # For details regarding fonts from package dependencies, 62 | # see https://flutter.io/custom-fonts/#from-packages 63 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:carousel_cards/main.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(new MyApp()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | --------------------------------------------------------------------------------