Skip to content

Commit

Permalink
Create CarouselView widget - Part 2 (flutter#149775)
Browse files Browse the repository at this point in the history
This PR is to create `Carousel.weighted` so the size of each carousel item is based on a list of weights. While scrolling, item sizes are changing dynamically based on the scrolling progress.

https://github.com/flutter/flutter/assets/36861262/181472b0-6f8b-48e7-b191-ab5f7c88c0c8
  • Loading branch information
QuncCccccc authored Jul 17, 2024
1 parent 6d0bb94 commit 844eb2f
Show file tree
Hide file tree
Showing 4 changed files with 1,761 additions and 118 deletions.
181 changes: 170 additions & 11 deletions examples/api/lib/material/carousel/carousel.0.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ class CarouselExampleApp extends StatelessWidget {
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Carousel Sample'),
leading: const Icon(Icons.cast),
title: const Text('Flutter TV'),
actions: const <Widget>[
Padding(
padding: EdgeInsetsDirectional.only(end: 16.0),
child: CircleAvatar(child: Icon(Icons.account_circle)),
),
],
),
body: const CarouselExample(),
),
Expand All @@ -33,19 +40,140 @@ class CarouselExample extends StatefulWidget {
}

class _CarouselExampleState extends State<CarouselExample> {
final CarouselController controller = CarouselController(initialItem: 1);

@override
void dispose() {
controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: CarouselView(
itemExtent: 330,
shrinkExtent: 200,
children: List<Widget>.generate(20, (int index) {
return UncontainedLayoutCard(index: index, label: 'Item $index');
}),
final double height = MediaQuery.sizeOf(context).height;

return ListView(
children: <Widget>[
ConstrainedBox(
constraints: BoxConstraints(maxHeight: height / 2),
child: CarouselView.weighted(
controller: controller,
itemSnapping: true,
flexWeights: const <int>[1, 7, 1],
children: ImageInfo.values.map((ImageInfo image) {
return HeroLayoutCard(imageInfo: image);
}).toList(),
),
),
),
const SizedBox(height: 20),
const Padding(
padding: EdgeInsetsDirectional.only(top: 8.0, start: 8.0),
child: Text('Multi-browse layout'),
),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 50),
child: CarouselView.weighted(
flexWeights: const <int>[1, 2, 3, 2, 1],
consumeMaxWeight: false,
children: List<Widget>.generate(20, (int index) {
return ColoredBox(
color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.8),
child: const SizedBox.expand(),
);
}),
),
),
const SizedBox(height: 20),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: CarouselView.weighted(
flexWeights: const <int>[3, 3, 3, 2, 1],
consumeMaxWeight: false,
children: CardInfo.values.map((CardInfo info) {
return ColoredBox(
color: info.backgroundColor,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(info.icon, color: info.color, size: 32.0),
Text(info.label, style: const TextStyle(fontWeight: FontWeight.bold), overflow: TextOverflow.clip, softWrap: false),
],
),
),
);
}).toList()
),
),
const SizedBox(height: 20),
const Padding(
padding: EdgeInsetsDirectional.only(top: 8.0, start: 8.0),
child: Text('Uncontained layout'),
),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: CarouselView(
itemExtent: 330,
shrinkExtent: 200,
children: List<Widget>.generate(20, (int index){
return UncontainedLayoutCard(index: index, label: 'Show $index');
}),
),
)
],
);
}
}

class HeroLayoutCard extends StatelessWidget {
const HeroLayoutCard({
super.key,
required this.imageInfo,
});

final ImageInfo imageInfo;

@override
Widget build(BuildContext context) {
final double width = MediaQuery.sizeOf(context).width;
return Stack(
alignment: AlignmentDirectional.bottomStart,
children: <Widget>[
ClipRect(
child: OverflowBox(
maxWidth: width * 7 / 8,
minWidth: width * 7 / 8,
child: Image(
fit: BoxFit.cover,
image: NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/material/${imageInfo.url}'
),
),
),
),
Padding(
padding: const EdgeInsets.all(18.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
imageInfo.title,
overflow: TextOverflow.clip,
softWrap: false,
style: Theme.of(context).textTheme.headlineLarge?.copyWith(color: Colors.white),
),
const SizedBox(height: 10),
Text(
imageInfo.subtitle,
overflow: TextOverflow.clip,
softWrap: false,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.white),
)
],
),
),
]
);
}
}
Expand Down Expand Up @@ -75,3 +203,34 @@ class UncontainedLayoutCard extends StatelessWidget {
);
}
}

enum CardInfo {
camera('Cameras', Icons.video_call, Color(0xff2354C7), Color(0xffECEFFD)),
lighting('Lighting', Icons.lightbulb, Color(0xff806C2A), Color(0xffFAEEDF)),
climate('Climate', Icons.thermostat, Color(0xffA44D2A), Color(0xffFAEDE7)),
wifi('Wifi', Icons.wifi, Color(0xff417345), Color(0xffE5F4E0)),
media('Media', Icons.library_music, Color(0xff2556C8), Color(0xffECEFFD)),
security('Security', Icons.crisis_alert, Color(0xff794C01), Color(0xffFAEEDF)),
safety('Safety', Icons.medical_services, Color(0xff2251C5), Color(0xffECEFFD)),
more('', Icons.add, Color(0xff201D1C), Color(0xffE3DFD8));

const CardInfo(this.label, this.icon, this.color, this.backgroundColor);
final String label;
final IconData icon;
final Color color;
final Color backgroundColor;
}

enum ImageInfo {
image0('The Flow', 'Sponsored | Season 1 Now Streaming', 'content_based_color_scheme_1.png'),
image1('Through the Pane', 'Sponsored | Season 1 Now Streaming', 'content_based_color_scheme_2.png'),
image2('Iridescence', 'Sponsored | Season 1 Now Streaming', 'content_based_color_scheme_3.png'),
image3('Sea Change', 'Sponsored | Season 1 Now Streaming', 'content_based_color_scheme_4.png'),
image4('Blue Symphony', 'Sponsored | Season 1 Now Streaming', 'content_based_color_scheme_5.png'),
image5('When It Rains', 'Sponsored | Season 1 Now Streaming', 'content_based_color_scheme_6.png');

const ImageInfo(this.title, this.subtitle, this.url);
final String title;
final String subtitle;
final String url;
}
32 changes: 29 additions & 3 deletions examples/api/test/material/carousel/carousel.0_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,44 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/carousel/carousel.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';

void main() {

// The app being tested loads images via HTTP which the test
// framework defeats by default.
setUpAll(() {
HttpOverrides.global = null;
});

testWidgets('Carousel Smoke Test', (WidgetTester tester) async {
await tester.pumpWidget(
const example.CarouselExampleApp(),
);
expect(find.byType(CarouselView), findsOneWidget);

expect(find.widgetWithText(example.UncontainedLayoutCard, 'Item 0'), findsOneWidget);
expect(find.widgetWithText(example.UncontainedLayoutCard, 'Item 1'), findsOneWidget);
expect(find.widgetWithText(example.HeroLayoutCard, 'Through the Pane'), findsOneWidget);
final Finder firstCarousel = find.byType(CarouselView).first;
await tester.drag(firstCarousel, const Offset(150, 0));
await tester.pumpAndSettle();
expect(find.widgetWithText(example.HeroLayoutCard, 'The Flow'), findsOneWidget);

await tester.drag(firstCarousel, const Offset(0, -200));
await tester.pumpAndSettle();

expect(find.widgetWithText(CarouselView, 'Cameras'), findsOneWidget);
expect(find.widgetWithText(CarouselView, 'Lighting'), findsOneWidget);
expect(find.widgetWithText(CarouselView, 'Climate'), findsOneWidget);
expect(find.widgetWithText(CarouselView, 'Wifi'), findsOneWidget);

await tester.drag(find.widgetWithText(CarouselView, 'Cameras'), const Offset(0, -200));
await tester.pumpAndSettle();

expect(find.text('Uncontained layout'), findsOneWidget);
expect(find.widgetWithText(CarouselView, 'Show 0'), findsOneWidget);
expect(find.widgetWithText(CarouselView, 'Show 1'), findsOneWidget);
});
}
Loading

0 comments on commit 844eb2f

Please sign in to comment.