Enhance Your Flutter App with the Powerful New CarouselView Widget

In the ever-evolving world of mobile development, Flutter consistently introduces innovative tools and widgets to enhance user experience. One such widget is the CarouselView, part of Material Design 3, which provides a flexible and visually appealing way to display a scrollable list of items. The CarouselView widget is designed to let you dynamically adjust the size of each item based on the chosen layout, making it ideal for a variety of use cases, from image galleries to feature highlights.

In this blog post, we will explore the different carousel layouts offered by the CarouselView widget, how to implement them in your Flutter application, and provide you with practical code examples to get started.

What is CarouselView?

The CarouselView widget in Flutter is a powerful UI component for displaying a horizontally scrollable list of items. It can be used in different layouts, each offering unique characteristics that suit various design needs. The flexibility of the CarouselView allows for dynamic item sizing and offers great customization potential, enabling developers to create visually rich, interactive experiences.

Material Design 3 Carousel Layouts

Material Design 3 introduces four distinct carousel layouts, which allow developers to present content in various formats based on the needs of the app. These layouts are supported by two main constructors in the CarouselViewwidget: CarouselView and CarouselView.weighted. Let's take a closer look at these layouts.

1. Multi-browse Layout

The Multi-browse layout shows at least one large, medium, and small item at a time. This layout works well for showcasing a variety of content with different sizes. The CarouselView.weighted constructor is used to create this layout, where each item is assigned a weight that determines its size relative to others.

Example Usage:

CarouselView.weighted(
  flexWeights: const <int>[1, 7, 1],
  children: ImageInfo.values.map((ImageInfo image) {
    return HeroLayoutCard(imageInfo: image);
  }).toList(),
)

2. Uncontained Layout (Default)

The Uncontained layout is the default layout for the CarouselView widget. This layout scrolls the items to the edge of the container, much like a ListView, where all items are of uniform size. It provides a classic carousel effect where each item occupies the entire width of the container.

Example Usage:

CarouselView(
  itemExtent: 330,
  shrinkExtent: 200,
  children: List<Widget>.generate(20, (int index) {
    return UncontainedLayoutCard(index: index, label: 'Show $index');
  }),
)

3. Hero Layout

The Hero layout is similar to the Multi-browse layout but shows at least one large and one small item at a time. This layout is also supported by CarouselView.weighted, and is perfect for applications that need to emphasize certain items while keeping others in view.

Example Usage:

CarouselView.weighted(
  flexWeights: const <int>[3, 3, 3, 2, 1],
  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(),
)

4. Full-Screen Layout

The Full-screen layout displays one large item that fills the screen edge-to-edge, and it scrolls vertically. This layout is useful when you need to create immersive experiences like a photo or video gallery. To achieve a full-screen layout, you can use either CarouselView or CarouselView.weighted.

  • Using CarouselView: Set the itemExtent to the screen size.
  • Using CarouselView.weighted: Set flexWeights to a single integer value, typically [1].

Example Usage:

CarouselView(
  itemExtent: MediaQuery.of(context).size.height, // Full screen
  children: List<Widget>.generate(5, (int index) {
    return Image.network('https://via.placeholder.com/150');
  }),
)

Constructor and Properties of CarouselView

Constructors

  1. CarouselView (Default Constructor): The default constructor implements the uncontained layout model. It provides basic carousel functionality with a uniform item size and allows items to scroll to the edge of the container.
  2. Constructor Syntax:
CarouselView({
  Key? key,
  required List<Widget> children,
  double itemExtent = 100.0,  // The width of each item
  double shrinkExtent = 0.0, // The shrinking effect of the items
});
  1. CarouselView.weighted (For Dynamic Item Sizing): This constructor allows you to create dynamic layouts, such as multi-browse and hero layouts. Each item is assigned a weight that determines the portion of the viewport it occupies.
  2. Constructor Syntax:
CarouselView.weighted({
  Key? key,
  required List<Widget> children,
  required List<int> flexWeights,  // Determines the size of each item
  bool consumeMaxWeight = true,    // Whether to consume all available space
});

Properties

  • itemExtent: The size of each item in the carousel. In the default constructor, this is the width of the item.
  • shrinkExtent: Defines how much the item can shrink while scrolling.
  • flexWeights: A list of weights that determine how much space each item should occupy in the carousel when using CarouselView.weighted.
  • children: A list of widgets to display in the carousel.

Methods

  • dispose(): Cleans up any resources used by the carousel. It is important to call this method when the widget is disposed of to prevent memory leaks.

Example Code

Below is a complete Flutter app demonstrating the use of CarouselView with different layouts:

import 'package:flutter/material.dart';


/// Flutter code sample for [CarouselView].


void main() => runApp(const CarouselExampleApp());


class CarouselExampleApp extends StatelessWidget {
  const CarouselExampleApp({super.key});


  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          leading: const Icon(Icons.menu),
          title: const Text('FlutterForGeeks'),
          actions: const <Widget>[
            Padding(
              padding: EdgeInsetsDirectional.only(end: 16.0),
              child: CircleAvatar(child: Icon(Icons.account_circle)),
            ),
          ],
        ),
        body: const CarouselExample(),
      ),
    );
  }
}


class CarouselExample extends StatefulWidget {
  const CarouselExample({super.key});


  @override
  State<CarouselExample> createState() => _CarouselExampleState();
}


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


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


  @override
  Widget build(BuildContext context) {
    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),
                )
              ],
            ),
          ),
        ]);
  }
}


class UncontainedLayoutCard extends StatelessWidget {
  const UncontainedLayoutCard({
    super.key,
    required this.index,
    required this.label,
  });


  final int index;
  final String label;


  @override
  Widget build(BuildContext context) {
    return ColoredBox(
      color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.5),
      child: Center(
        child: Text(
          label,
          style: const TextStyle(color: Colors.white, fontSize: 20),
          overflow: TextOverflow.clip,
          softWrap: false,
        ),
      ),
    );
  }
}


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;
}

Conclusion

The CarouselView widget offers a modern, flexible solution for displaying scrollable lists of items in Flutter apps. With support for multiple layouts like multi-browseuncontainedhero, and full-screen, it’s easy to create dynamic, engaging interfaces that respond to user interaction. Whether you're building a media gallery, product carousel, or feature highlight, the CarouselView widget helps you present content in visually stunning ways.

By mastering these layouts and understanding how to control item sizing using the flexWeights property, you can create a wide variety of engaging experiences tailored to your app's needs.


Youtube Video



Description of the image

Related Posts