布局
布局
UITableView 和 UICollectionView 相当于 Flutter 中的什么?
在 iOS 里,你可能使用 UITableView 或者 UICollectionView 来展示一个列表。而在 Flutter 里,你可以使用 ListView 来达到类似的实现。在 iOS 中,你通过 delegate 方法来确定显示的行数,相应位置的 cell,以及 cell 的尺寸。
由于 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()),
);
}
_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;
}
}
如何确定列表中被点击的元素?
在 iOS 里,可以通过 tableView:didSelectRowAtIndexPath: 代理方法来实现。而在 Flutter 里,需要通过 widget 传递进来的 touch 响应处理来实现。
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()),
);
}
_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;
}
}
如何动态更新 ListView?
在 iOS 里,可以更新列表的数据,然后通过调用 reloadData 方法来通知 tableView 或者 collectionView。
在 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 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 + 1));
print('row $i');
});
},
);
}
}
一个推荐的、高效且有效的方法就是使用 ListView.Builder 来构建列表。当你的数据量很大,且需要构建动态列表时,这个方法会非常好用。
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 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 + 1));
print('row $i');
});
},
);
}
}
和创建 ListVie 不同,创建 ListView.Builder 需要两个关键参数:初始化列表长度和 ItemBuilder 函数。
ItemBuilder 函数和 iOS 里 tableView 和 collectionView 的 cellForItemAt 方法类似,它接收位置参数,然后返回想要在该位置渲染的 cell。
最后,也是最重要的,注意 onTap() 方法并没有重新创建列表,而是使用 .add 方法进行添加。
ScrollView 相当于 Flutter 中的什么?
在 iOS 里,把视图放在 ScrollView 里来允许用户在需要时滚动内容。
在 Flutter 中,最简单的办法就是使用 ListView widget。它和 iOS 中的 ScrollView 以及 TableView 表现一致,也可以给它的子 widget 做垂直排版。
@overrideWidget build(BuildContext context) { return ListView( children: <Widget>[ Text('Row One'), Text('Row Two'), Text('Row Three'), Text('Row Four'), ], );}
关于 Flutter 中布局的更多细节,请参看 布局教程。
