目标:1)项目中,数据源可能涉及不同的模版,显示不同类型的子项,类似RecycleView的itemType, 有多种类型,列表怎么显示?
2)不同的数据源构建列表
一、创建不同的数据源
采用类似RecyclerView的思想,不同的数据源,对应不同的子项Widget进行展现;
因为需要列表项统一展现,因此最好抽取一个子项的公共基类,进行统一处理。
1.1 创建数据源基类
抽取统一基类,列表项显示主标题和副标题。
- 一种子项只显示主标题;
- 另一类子项显示主标题和副标题
/// The base class for the different types of items the list can contain.
abstract class ListItem {
/// The title line to show in a list item.
Widget buildTitle(BuildContext context);
/// The subtitle line, if any, to show in a list item.
Widget buildSubtitle(BuildContext context);
}
1.2 创建不同数据源
只有主标题的数据源
/**
* 为什么使用implements,而不是extends?
* 因为是ListItem的实现类,已经实现ListItem的抽象方法了。
* 如果是extends, 意味着HeadingItem不需要实现抽象方法,自身仍然作为抽象类使用
*/
class HeadingItem implements ListItem {
final String title;
/**
* 为什么需要const修饰符?
*/
const HeadingItem(this.title);
@override
Widget buildSubtitle(BuildContext context) {
// 创建主标题
return Text(
title,
style: Theme.of(context).textTheme.headlineSmall,
);
}
@override
Widget buildTitle(BuildContext context) {
return const SizedBox.shrink();
}
}
包含主标题和副标题的数据源
/**
* 含有主标题和副标题的列表项
*/
class MessageItem implements ListItem {
final String title;
final String message;
const MessageItem(this.title, this.message);
@override
Widget buildSubtitle(BuildContext context) {
// 创建主标题
return Text(
title,
style: Theme.of(context).textTheme.headlineSmall,
);
}
@override
Widget buildTitle(BuildContext context) {
// 创建副标题
return Text(
message,
style: Theme.of(context).textTheme.headlineSmall,
);
}
}
二、创建不同数据源的列表
我们知道ListView的子项是ListTile,因此我们需要将数据源填充到ListTile中。
void main() {
runApp(
MyApp(),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a purple toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
@immutable
class MyHomePage extends StatefulWidget {
List<ListItem> items = List<ListItem>.generate(
1000,
(i) => i % 6 == 0
? HeadingItem('Heading $i')
: MessageItem('Sender $i', 'Message body $i'),
);
MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
// 根据数据源创建不同项的列表
body: ListView.builder(
// 列表项个数
itemCount: widget.items.length,
// 列表项构建器
itemBuilder: (context, index) {
// 返回列表项的ListTile
return ListTile(
// 主标题(通过ListItem创建主标题)
title: widget.items[index].buildTitle(context),
subtitle: widget.items[index].buildSubtitle(context),
);
},
).build(context),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
创建List Tile的时候,采用数据源ListItem进行填充,因为数据源有共同的基类,因此构建ListTile的时候就很方便。
// 根据数据源创建不同项的列表
body: ListView.builder(
// 列表项个数
itemCount: widget.items.length,
// 列表项构建器
itemBuilder: (context, index) {
// 返回列表项的ListTile
return ListTile(
// 主标题(通过ListItem创建主标题)
title: widget.items[index].buildTitle(context),
subtitle: widget.items[index].buildSubtitle(context),
);
},
).build(context),
以下是展现效果。