| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | import 'package:flutter/material.dart'; |
| 6 | |
| 7 | /// Flutter code sample for [CarouselView]. |
| 8 | |
| 9 | void main() => runApp(const CarouselExampleApp()); |
| 10 | |
| 11 | class CarouselExampleApp extends StatelessWidget { |
| 12 | const CarouselExampleApp({super.key}); |
| 13 | |
| 14 | @override |
| 15 | Widget build(BuildContext context) { |
| 16 | return MaterialApp( |
| 17 | debugShowCheckedModeBanner: false, |
| 18 | home: Scaffold( |
| 19 | appBar: AppBar( |
| 20 | leading: const Icon(Icons.cast), |
| 21 | title: const Text('Flutter TV' ), |
| 22 | actions: const <Widget>[ |
| 23 | Padding( |
| 24 | padding: EdgeInsetsDirectional.only(end: 16.0), |
| 25 | child: CircleAvatar(child: Icon(Icons.account_circle)), |
| 26 | ), |
| 27 | ], |
| 28 | ), |
| 29 | body: const CarouselExample(), |
| 30 | ), |
| 31 | ); |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | class CarouselExample extends StatefulWidget { |
| 36 | const CarouselExample({super.key}); |
| 37 | |
| 38 | @override |
| 39 | State<CarouselExample> createState() => _CarouselExampleState(); |
| 40 | } |
| 41 | |
| 42 | class _CarouselExampleState extends State<CarouselExample> { |
| 43 | final CarouselController controller = CarouselController(initialItem: 1); |
| 44 | |
| 45 | @override |
| 46 | void dispose() { |
| 47 | controller.dispose(); |
| 48 | super.dispose(); |
| 49 | } |
| 50 | |
| 51 | @override |
| 52 | Widget build(BuildContext context) { |
| 53 | final double height = MediaQuery.sizeOf(context).height; |
| 54 | |
| 55 | return ListView( |
| 56 | children: <Widget>[ |
| 57 | ConstrainedBox( |
| 58 | constraints: BoxConstraints(maxHeight: height / 2), |
| 59 | child: CarouselView.weighted( |
| 60 | controller: controller, |
| 61 | itemSnapping: true, |
| 62 | flexWeights: const <int>[1, 7, 1], |
| 63 | children: ImageInfo.values.map((ImageInfo image) { |
| 64 | return HeroLayoutCard(imageInfo: image); |
| 65 | }).toList(), |
| 66 | ), |
| 67 | ), |
| 68 | const SizedBox(height: 20), |
| 69 | const Padding( |
| 70 | padding: EdgeInsetsDirectional.only(top: 8.0, start: 8.0), |
| 71 | child: Text('Multi-browse layout' ), |
| 72 | ), |
| 73 | ConstrainedBox( |
| 74 | constraints: const BoxConstraints(maxHeight: 50), |
| 75 | child: CarouselView.weighted( |
| 76 | flexWeights: const <int>[1, 2, 3, 2, 1], |
| 77 | consumeMaxWeight: false, |
| 78 | children: List<Widget>.generate(20, (int index) { |
| 79 | return ColoredBox( |
| 80 | color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.8), |
| 81 | child: const SizedBox.expand(), |
| 82 | ); |
| 83 | }), |
| 84 | ), |
| 85 | ), |
| 86 | const SizedBox(height: 20), |
| 87 | ConstrainedBox( |
| 88 | constraints: const BoxConstraints(maxHeight: 200), |
| 89 | child: CarouselView.weighted( |
| 90 | flexWeights: const <int>[3, 3, 3, 2, 1], |
| 91 | consumeMaxWeight: false, |
| 92 | children: CardInfo.values.map((CardInfo info) { |
| 93 | return ColoredBox( |
| 94 | color: info.backgroundColor, |
| 95 | child: Center( |
| 96 | child: Column( |
| 97 | mainAxisAlignment: MainAxisAlignment.center, |
| 98 | children: <Widget>[ |
| 99 | Icon(info.icon, color: info.color, size: 32.0), |
| 100 | Text( |
| 101 | info.label, |
| 102 | style: const TextStyle(fontWeight: FontWeight.bold), |
| 103 | overflow: TextOverflow.clip, |
| 104 | softWrap: false, |
| 105 | ), |
| 106 | ], |
| 107 | ), |
| 108 | ), |
| 109 | ); |
| 110 | }).toList(), |
| 111 | ), |
| 112 | ), |
| 113 | const SizedBox(height: 20), |
| 114 | const Padding( |
| 115 | padding: EdgeInsetsDirectional.only(top: 8.0, start: 8.0), |
| 116 | child: Text('Uncontained layout' ), |
| 117 | ), |
| 118 | ConstrainedBox( |
| 119 | constraints: const BoxConstraints(maxHeight: 200), |
| 120 | child: CarouselView( |
| 121 | itemExtent: 330, |
| 122 | shrinkExtent: 200, |
| 123 | children: List<Widget>.generate(20, (int index) { |
| 124 | return UncontainedLayoutCard(index: index, label: 'Show $index' ); |
| 125 | }), |
| 126 | ), |
| 127 | ), |
| 128 | ], |
| 129 | ); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | class HeroLayoutCard extends StatelessWidget { |
| 134 | const HeroLayoutCard({super.key, required this.imageInfo}); |
| 135 | |
| 136 | final ImageInfo imageInfo; |
| 137 | |
| 138 | @override |
| 139 | Widget build(BuildContext context) { |
| 140 | final double width = MediaQuery.sizeOf(context).width; |
| 141 | return Stack( |
| 142 | alignment: AlignmentDirectional.bottomStart, |
| 143 | children: <Widget>[ |
| 144 | ClipRect( |
| 145 | child: OverflowBox( |
| 146 | maxWidth: width * 7 / 8, |
| 147 | minWidth: width * 7 / 8, |
| 148 | child: Image( |
| 149 | fit: BoxFit.cover, |
| 150 | image: NetworkImage( |
| 151 | 'https://flutter.github.io/assets-for-api-docs/assets/material/${imageInfo.url}', |
| 152 | ), |
| 153 | ), |
| 154 | ), |
| 155 | ), |
| 156 | Padding( |
| 157 | padding: const EdgeInsets.all(18.0), |
| 158 | child: Column( |
| 159 | crossAxisAlignment: CrossAxisAlignment.start, |
| 160 | mainAxisSize: MainAxisSize.min, |
| 161 | children: <Widget>[ |
| 162 | Text( |
| 163 | imageInfo.title, |
| 164 | overflow: TextOverflow.clip, |
| 165 | softWrap: false, |
| 166 | style: Theme.of(context).textTheme.headlineLarge?.copyWith(color: Colors.white), |
| 167 | ), |
| 168 | const SizedBox(height: 10), |
| 169 | Text( |
| 170 | imageInfo.subtitle, |
| 171 | overflow: TextOverflow.clip, |
| 172 | softWrap: false, |
| 173 | style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.white), |
| 174 | ), |
| 175 | ], |
| 176 | ), |
| 177 | ), |
| 178 | ], |
| 179 | ); |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | class UncontainedLayoutCard extends StatelessWidget { |
| 184 | const UncontainedLayoutCard({super.key, required this.index, required this.label}); |
| 185 | |
| 186 | final int index; |
| 187 | final String label; |
| 188 | |
| 189 | @override |
| 190 | Widget build(BuildContext context) { |
| 191 | return ColoredBox( |
| 192 | color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.5), |
| 193 | child: Center( |
| 194 | child: Text( |
| 195 | label, |
| 196 | style: const TextStyle(color: Colors.white, fontSize: 20), |
| 197 | overflow: TextOverflow.clip, |
| 198 | softWrap: false, |
| 199 | ), |
| 200 | ), |
| 201 | ); |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | enum CardInfo { |
| 206 | camera('Cameras', Icons.video_call, Color(0xff2354C7), Color(0xffECEFFD)), |
| 207 | lighting('Lighting', Icons.lightbulb, Color(0xff806C2A), Color(0xffFAEEDF)), |
| 208 | climate('Climate', Icons.thermostat, Color(0xffA44D2A), Color(0xffFAEDE7)), |
| 209 | wifi('Wifi', Icons.wifi, Color(0xff417345), Color(0xffE5F4E0)), |
| 210 | media('Media', Icons.library_music, Color(0xff2556C8), Color(0xffECEFFD)), |
| 211 | security('Security', Icons.crisis_alert, Color(0xff794C01), Color(0xffFAEEDF)), |
| 212 | safety('Safety', Icons.medical_services, Color(0xff2251C5), Color(0xffECEFFD)), |
| 213 | more('', Icons.add, Color(0xff201D1C), Color(0xffE3DFD8)); |
| 214 | |
| 215 | const CardInfo(this.label, this.icon, this.color, this.backgroundColor); |
| 216 | final String label; |
| 217 | final IconData icon; |
| 218 | final Color color; |
| 219 | final Color backgroundColor; |
| 220 | } |
| 221 | |
| 222 | enum ImageInfo { |
| 223 | image0('The Flow', 'Sponsored | Season 1 Now Streaming', 'content_based_color_scheme_1.png'), |
| 224 | image1( |
| 225 | 'Through the Pane', |
| 226 | 'Sponsored | Season 1 Now Streaming', |
| 227 | 'content_based_color_scheme_2.png', |
| 228 | ), |
| 229 | image2('Iridescence', 'Sponsored | Season 1 Now Streaming', 'content_based_color_scheme_3.png'), |
| 230 | image3('Sea Change', 'Sponsored | Season 1 Now Streaming', 'content_based_color_scheme_4.png'), |
| 231 | image4('Blue Symphony', 'Sponsored | Season 1 Now Streaming', 'content_based_color_scheme_5.png'), |
| 232 | image5('When It Rains', 'Sponsored | Season 1 Now Streaming', 'content_based_color_scheme_6.png'); |
| 233 | |
| 234 | const ImageInfo(this.title, this.subtitle, this.url); |
| 235 | final String title; |
| 236 | final String subtitle; |
| 237 | final String url; |
| 238 | } |
| 239 | |