1. ListView 在 Flutter 中的对应概念是什么?
Flutter 中 ListView 的对应概念仍然是 ListView!
使用 Android 的 ListView 时,创建一个 adapter 并将其传给 ListView, ListView 渲染 adapter 返回的每一行内容。然后,你需要确保回收了每一行视图,否则,你会遇到各种奇怪的界面和内存问题。
因为 Flutter widget 不可变的特点,你需要向 ListView 传入一组 widget, Flutter 会保证滑动的快速顺畅。
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView(children: _getListData()),
);
}
List<Widget> _getListData() {
List<Widget> widgets = [];
for (int i = 0; i < 100; i++) {
widgets.add(Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row $i"),
));
}
return widgets;
}
}
2. 如何知道点击了哪个列表项?
在 Android 中,ListView 有一个可以帮助你定位哪个列表项被点击了的方法 onItemClickListener。在 Flutter 中,则使用传入 widget 的触摸监听。
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView(children: _getListData()),
);
}
List<Widget> _getListData() {
List<Widget> widgets = [];
for (int i = 0; i < 100; i++) {
widgets.add(GestureDetector(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row $i"),
),
onTap: () {
print('row tapped');
},
));
}
return widgets;
}
}
3. 如何动态更新 ListView?
在 Android 中,你需要更新 adapter 并调用 notifyDataSetChanged。
在 Flutter 中,如果你准备在 setState() 里更新一组 widget,你很快会发现你的数据并没有更新到界面上。这是因为当 setState() 被调用的时候, Flutter 渲染引擎会查看 Widget 树是否有任何更改。当引擎检查到 ListView,他会执行 == 检查,并判断两个 ListView 是一样的。没有任何更改,所以也就不需要更新。
更新 ListView 的一个简单方法是,在 setState() 里创建一个新的 List,并将数据从旧列表拷贝到新列表。虽然这个方法很简单,就如下面例子所示,但是并不推荐在大数据集的时候使用。
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List<Widget> widgets = [];
@override
void initState() {
super.initState();
for (int i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView(children: widgets),
);
}
Widget getRow(int i) {
return GestureDetector(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row $i"),
),
onTap: () {
setState(() {
widgets = List.from(widgets);
widgets.add(getRow(widgets.length));
print('row $i');
});
},
);
}
}
推荐的高效且有效的创建一个列表的方法是使用 ListView.Builder。这个方法非常适用于动态列表或者拥有大量数据的列表。这基本上就是 Android 里的 RecyclerView,会为你自动回收列表项:
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List<Widget> widgets = [];
@override
void initState() {
super.initState();
for (int i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
}));
}
Widget getRow(int i) {
return GestureDetector(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row $i"),
),
onTap: () {
setState(() {
widgets.add(getRow(widgets.length));
print('row $i');
});
},
);
}
}
不用创建一个 “ListView”,而是创建接收两个参数的 ListView.Builder,两个参数分别是列表的初始长度和一个 ItemBuilder 方法。
ItemBuilder 方法和 Android adapter 里的 getView 方法类似;它通过位置返回你期望在这个位置渲染的列表项。
最后也是最重要的一条,需要注意 onTap() 方法不再重建列表项,但是会执行 .add 操作。