Flutter 布局常用的 widgets(Common layout widgets)

本文最后更新于:2018年9月21日 上午

简单列举总结一下常用的布局widget。
Flutter有丰富的layout组件库。其中有一些是常用库。
下面的widget分为两类:标准组件和来自Material Components的特殊组件。
只有Material App能够使用Material Components的组件。

标准组件 - Standard widgets

  • Container
    • 给一个组件添加 padding, margins, 边界(borders), 背景颜色或其它装饰(decorations)。
  • GridView
    • 将多个widget放在一个可滑动的表格中。
  • ListView
    • 将多个widget放在一个可滑动的列表中。
  • Stack
    • 在一个widget上面盖上另一个widget。

Material Components

  • Card
    • 将一些相近的信息装进一个有圆角和阴影的盒子里。
  • ListTile
    • 一个Row中装载最多3行文字;可选则在前面或尾部添加图标。

Container

Container用法比较自由。可以把整个layout放进container里面,然后改变背景颜色或图片。

Container 小结:

  • 添加 padding, margins, 和边界(borders)
  • 能够更好背景颜色和图片
  • 包含一个单独的子widget,这个子widget可以是Row、Column或一个widget树的根widget

container结构

测试代码widgetdemo/container_page.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import 'package:flutter/material.dart';
import 'package:demo_flutter/widgetdemo/container_page.dart';
// 引入自定义的包......

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Container demo 1',
theme: new ThemeData(primarySwatch: Colors.brown),
home: new ContainerDemoPage(), // 这里换上想要测试的界面
);
}
}

widgetdemo/container_page.dart代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import 'package:flutter/material.dart';

/// container示例界面
class ContainerDemoPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => new _ContainerDemoPageState();
}

class _ContainerDemoPageState extends State<ContainerDemoPage> {
@override
Widget build(BuildContext context) {
Expanded imageExpanded(String img) {
return new Expanded(child: new Container(
decoration: new BoxDecoration(
border: new Border.all(width: 10.0, color: Colors.black38),
borderRadius: const BorderRadius.all(
const Radius.circular(8.0))),
margin: const EdgeInsets.all(4.0),
child: new Image.asset(img),
));
}

var container = new Container(
decoration: new BoxDecoration(color: Colors.black26),
child: new Column(
children: <Widget>[
new Row(children: <Widget>[
imageExpanded('images/c1.jpg'),
imageExpanded('images/c2.jpg'),
],),
new Row(children: <Widget>[
imageExpanded('images/d1.jpg'),
imageExpanded('images/d2.jpg'),
],),
new Row(children: <Widget>[
imageExpanded('images/p1.jpg'),
],)
],
),
);

return new Scaffold(
appBar: new AppBar(title: new Text('Container Page demo'),),
body: new Center(
child: container,
),
);
}
}

container示例

GridView

用GridView来将widget放入一个2维的列表中。
GridView提供了2个预装配好的列表,也可以自己建立自定义列表。
GridView支持滚动。

GridView 小结:

  • 将多个widget放进一个表格中
  • 当超出渲染范围时,自动提供滚动功能
  • 可自定义格子,也可用下面提供的2种
    • GridView.count 指定列的数目
    • GridView.extent 允许指定子项的最大像素宽度

示例1 - 用GridView.extent

GridView.extent指定子项占据的最大宽度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import 'package:flutter/material.dart';

/// gridView示例界面1
class GridDemo1Page extends StatefulWidget {
@override
State<StatefulWidget> createState() => new _GridDemo1PageState();
}

class _GridDemo1PageState extends State<GridDemo1Page> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('Grid Page 1 demo'),),
body: new Center(
child: buildGrid(),
),
);
}

List<Container> _buildGridTileList(int count) {
return new List<Container>.generate(count, (int index) =>
new Container(child: new Image.asset('images/pic${index + 1}.jpg'),));
}

Widget buildGrid() {
return new GridView.extent(
maxCrossAxisExtent: 150.0,
padding: const EdgeInsets.all(4.0),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
children: _buildGridTileList(30),);
}
}

用GridView.extent

示例2 - 用GridView.count

crossAxisCount设为2,分成2列。

1
2
3
4
5
6
7
8
9
10
11
Widget buildGrid() {
var countGrid = GridView.count(
crossAxisCount: 2,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
padding: const EdgeInsets.all(4.0),
childAspectRatio: 1.3,
children: _buildGridTileList(30),
);
return countGrid;
}

GridView.count示例

ListView

ListView能以列的形式展示数据。当内容超过渲染范围时,自动提供滚动的功能。

ListView 小结

  • 把子视图装进列表中
  • 水平或竖直都可以
  • 支持滑动
  • 相比于Column,可选配置比较少,但更易用并且支持滑动

和Android中的ListView差别不大

示例1

ListTile当做子项来装载数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import 'package:flutter/material.dart';

class ListViewPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => new _ListViewPageState();
}

class _ListViewPageState extends State<ListViewPage> {

@override
Widget build(BuildContext context) {
List<Widget> list = <Widget>[];
for (int i = 0; i < 30; i++) {
list.add(new ListTile(
title: new Text('title$i', style: _itemTextStyle,),
subtitle: new Text('A'),
leading: i % 3 == 0
? new Icon(Icons.theaters, color: Colors.blue,)
: new Icon(Icons.restaurant, color: Colors.blue,),
));
}
return new Scaffold(
appBar: new AppBar(title: new Text('ListView Demo'),),
body: new Center(child: new ListView(children: list,),),
);
}
}

TextStyle _itemTextStyle = new TextStyle(
fontWeight: FontWeight.w500, fontSize: 14.0);

ListView参考效果图1

另外可以参考 https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/demo/colors_demo.dart

Stack

使用Stack在widget之上显示另一些widget,通常用来显示图片。
显示的widget可以完全地把底部widget盖住。

Stack 小结:

  • 用来在当前widget上面再盖上一层widget
  • Stack children中的第一个widget放在最下,后面的widget会一层层盖上去
  • Stack的内容不支持滚动
  • 可以裁剪超出范围的子widget

Stack示例1

显示一个CircleAvatar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import 'package:flutter/material.dart';

class StackPage1 extends StatefulWidget {
@override
State<StatefulWidget> createState() => new _StackPage1State();
}

class _StackPage1State extends State<StackPage1> {

@override
Widget build(BuildContext context) {
var stack = new Stack(
alignment: const Alignment(0.6, 0.6),
children: <Widget>[
new CircleAvatar(
backgroundImage: new AssetImage('images/android_1.jpg'),
radius: 100.0,),
new Container(decoration: new BoxDecoration(color: Colors.black45),
child: new Text(
'Android Avatar', style: new TextStyle(color: Colors.white70),),),
new Container(decoration: new BoxDecoration(color: Colors.transparent),
padding: const EdgeInsets.fromLTRB(0.0, 0.0, 100.0, 0.0),
child: new CircleAvatar(
backgroundImage: new AssetImage('images/p_box1.png'),
backgroundColor: Colors.transparent,
radius: 10.0,),),
],
);
return new Scaffold(
appBar: new AppBar(title: new Text('Stack Demo 1'),),
body: new Center(child: stack,),
);
}
}

Stack示例1

Card

Card来自Material组件库,可包含一些数据,通常用ListTile来组装。Card只有一个子widget,可以是column、row、list、grid或其它组合widget。
默认情况下,Card把自己的尺寸缩小为0像素。可以用SizedBox来指定card的尺寸。

Flutter中的Card有圆角和阴影效果。修改elevation可改变阴影效果。

elevation取值范围,参考 Elevation and Shadows

若设置的范围外的值,阴影效果会消失。

Card 小结:

  • 实现了Material Design card
  • 用于展示相关的数据
  • 有一个子项(child),可以是column、row、list、grid或其它组合widget
  • 有圆角和阴影效果
  • 不支持滚动

Card示例1

将前面的ListView示例修改一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import 'package:flutter/material.dart';

class ListViewPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => new _ListViewPageState();
}

class _ListViewPageState extends State<ListViewPage> {

@override
Widget build(BuildContext context) {
List<Widget> list = <Widget>[];
for (int i = 0; i < 30; i++) {
list.add(new Card(child: new Column(
children: <Widget>[
new Image.asset(
'images/pic${i + 1}.jpg',),
new ListTile(
title: new Text('title$i', style: _itemTextStyle,),
subtitle: new Text('A'),
leading: i % 3 == 0
? new Icon(Icons.theaters, color: Colors.blue,)
: new Icon(Icons.restaurant, color: Colors.blue,),
),
],
),));
}
return new Scaffold(
appBar: new AppBar(title: new Text('ListView Demo'),),
body: new Center(child: new ListView(children: list,),),
);
}
}

TextStyle _itemTextStyle = new TextStyle(
fontWeight: FontWeight.w500, fontSize: 14.0);

Card示例1

ListTile

来自Material组件库的横向组件。可自定义3行文字及其可选的头尾图标。
此控件常与Card或ListView一起用。

ListTile 小结:

  • 可定制3行带图标的文字
  • 相比于Row,配置更少,但更易用

加一个主界面

放置一些按钮,点击跳转到相应的界面。
使用Navigator.of(context).pushNamed(routeName)来跳转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import 'package:flutter/material.dart';
import 'package:demo_flutter/widgetdemo/container_page.dart';
import 'package:demo_flutter/widgetdemo/grid_page.dart';
import 'package:demo_flutter/widgetdemo/listview_demo.dart';
import 'package:demo_flutter/widgetdemo/stack_page1.dart';
import 'package:demo_flutter/widgetdemo/button_page.dart';

const String CONTAINER_DEMO_PAGE = '/a';

void main() {
runApp(new MaterialApp(
home: new HomePage(),
routes: {
CONTAINER_DEMO_PAGE: (BuildContext context) => new ContainerDemoPage(),
'/b': (BuildContext context) => new GridDemo1Page(),
'/c': (BuildContext context) => new ListViewPage(),
'/d': (BuildContext context) => new StackPage1(),
'/e': (BuildContext context) => new ButtonPage(),
},
));
}

class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => new _HomePageState();
}

class _HomePageState extends State<HomePage> {

@override
Widget build(BuildContext context) {
getGestureDetector(String routeName, String content) {
return new GestureDetector (
onTap: () {
Navigator.of(context).pushNamed(routeName);
},
child: new Container (
padding: EdgeInsets.all(20.0),
child: new Center(child: new Text (content),)),
);
}
return new Scaffold(
appBar: new AppBar(title: new Text('Home'),),
body: new Column(children: <Widget>[
getGestureDetector(CONTAINER_DEMO_PAGE, 'Container Demo'),
getGestureDetector('/b', 'Grid Demo 1'),
getGestureDetector('/c', 'ListView Demo'),
getGestureDetector('/d', 'Stack Demo'),
getGestureDetector('/e', 'Button Page'),
],),
);
}

}


Flutter 布局常用的 widgets(Common layout widgets)
https://blog.rustfisher.com/2018/06/26/Flutter/Flutter-Layout_common_widgets/
作者
Rust Fisher
发布于
2018年6月26日
许可协议