-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Use case
Commonly, widgets like PageView and ListView lazily load items that are not in view. However, CarouselView does not. This can make things like fetching images from the internet or having to do heavy computations to get the widgets feel sluggish and can be taxing on the system if there are a lot of elements in the carousel.
However, it can be easier to use a PageView.builder for some cases, but this method enforces page snapping and additionally, the developer would need to program in the material effects of the CarouselView themselves which can be not trivial.
Description
The current implementation of CarouselView does not have a rigid form of lazy loading process akin to something like ListView.builder which allows for items in the list to load when they are deemed visible by Flutter.
Here is a demonstration of the difference between CarouselView and ListView.builder at handling 30 FutureBuilders.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: MainApp()));
}
final class MainApp extends StatefulWidget {
const MainApp({super.key});
@override
State<MainApp> createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
bool showListView = false;
Axis dir = Axis.vertical;
late List<Future<void>> _carouselFutures;
static const int totalItems = 30;
static const Duration futureDelayMS = Duration(milliseconds: 3500);
@override
void initState() {
super.initState();
_carouselFutures = List<Future<void>>.generate(
totalItems,
(int index) =>
Future<void>.delayed(futureDelayMS, () => print("(CarouselView) $index finished!")),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: const SizedBox.shrink(),
flexibleSpace: SafeArea(
child: Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ElevatedButton(
onPressed: () => setState(() {
showListView = !showListView;
if (!showListView) {
_carouselFutures = List<Future<void>>.generate(
totalItems,
(int index) => Future<void>.delayed(
futureDelayMS,
() => print("(CarouselView) $index finished!"),
),
);
}
}),
child: Text("Show ${showListView ? "CarouselView" : "ListView"}"),
),
ElevatedButton(
onPressed: () =>
setState(() => dir = dir == Axis.horizontal ? Axis.vertical : Axis.horizontal),
child: Text("Set ${dir == Axis.horizontal ? "Vertical" : "Horizontal"}"),
),
],
),
),
),
body: SafeArea(
left: false,
right: false,
child: Material(
child: Column(
children: <Widget>[
Expanded(
child: !showListView
?
// carousel view here
CarouselView.weighted(
scrollDirection: dir,
flexWeights: const <int>[1, 2, 1],
itemSnapping: true,
shape: Border.all(color: Colors.transparent),
children: List<Widget>.generate(totalItems, (int index) {
return SizedBox(
height: MediaQuery.sizeOf(context).height,
width: MediaQuery.sizeOf(context).width * 0.95,
child: FutureBuilder<void>(
future: _carouselFutures[index],
builder: (BuildContext context, AsyncSnapshot<void> asyncSnapshot) {
if (asyncSnapshot.hasData ||
asyncSnapshot.connectionState == ConnectionState.done) {
return Container(
decoration: BoxDecoration(
color: index % 2 == 0 ? Colors.red : Colors.green,
borderRadius: BorderRadius.circular(30),
),
child: Center(
child: Text(
index.toString(),
textAlign: TextAlign.center,
textWidthBasis: TextWidthBasis.longestLine,
style: const TextStyle(
fontSize: 64,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
);
} else if (asyncSnapshot.hasError) {
return const Text(
"Failed to load!",
style: TextStyle(color: Colors.red),
);
}
return const CircularProgressIndicator(color: Colors.amber);
},
),
);
}),
)
:
// listview method
ListView.builder(
scrollDirection: dir,
itemCount: totalItems,
itemBuilder: (BuildContext context, int index) {
return SizedBox(
height: MediaQuery.sizeOf(context).height,
width: MediaQuery.sizeOf(context).width * 0.95,
child: FutureBuilder<void>(
future: Future<void>.delayed(
futureDelayMS,
() => print("(ListView) $index finished!"),
),
builder: (BuildContext context, AsyncSnapshot<void> asyncSnapshot) {
if (asyncSnapshot.connectionState == ConnectionState.done) {
return Container(
decoration: BoxDecoration(
color: index % 2 == 0 ? Colors.red : Colors.green,
borderRadius: BorderRadius.circular(30),
),
child: Center(
child: Text(
index.toString(),
style: const TextStyle(
fontSize: 64,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
),
);
} else if (asyncSnapshot.hasError) {
return const Text(
"Failed to load!",
style: TextStyle(color: Colors.red),
);
}
return const CircularProgressIndicator(color: Colors.amber);
},
),
);
},
),
),
],
),
),
),
);
}
}Proposal
Using the code sample from above, it would be better to have some kind of way to build the items on demand with like a CarouselView.builder and CarouselView.buildWeighted that can be supplied a common Widget Function(BuildContext, int) to render the widget at that index.