Flutter 中拥有需要将近30种内置的 布局Widget,其中常用有 Container、Padding、Center、Flex、Stack、Row、Column、ListView 等,下面简单讲解它们的特性和使用。

概述

标准组件-Standard widgets

Container

给一个组件添加padding,margins,边界(borders),背景颜色或其它装饰(decorations)。

GridView

将多个widget放在一个可滑动的表格中

ListView

将多个widget放在一个可滑动列表中

Stack

在一个widget上面覆盖另外一个widget

Material Components

只有Material App能够使用Material Components的组件。

Card

将一些相近的信息封装进一个有圆角的阴影的盒子里。

ListTile

一个Row中装载最多3行文字,可选则在前面或者尾部添加图标。

Container

最常用的默认布局,只能包含一个child,支持配置padding,margin,color,width,height,decoration(一般配置边框和阴影)等配置,在Flutter中,不是所有的控件都有宽高、padding、margin、color等属性,所以才会有Padding,Center等Widget的存在。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Container(
    //四周10大小的margin
    margin: EdgeInsets.all(10.0),
    height: 120.0,
    width: 500.0,
    //透明黑色遮罩
    decoration: BoxDecoration(
    borderRadius: BorderRadius.all(Radius.circular(4.0)),
    color: Colors.black,
    border: Border.all(color: Colors.blue,width: 0.3)
    ),
    child: Text("6666",
    style: TextStyle(color: Colors.red),),
),

decoration: Decorations.bottom, 底部横线

Column、Row

它们常用的有这些属性配置:主轴方向是 start 或 center 等;副轴方向方向是 start 或 center 等;mainAxisSize 是充满最大尺寸,或者只根据子 Widget 显示最小尺寸。

日常使用场景配置:

1
2
3
4
5
6
//主轴方向,Column的竖向、Row我的横向
mainAxisAlignment: MainAxisAlignment.start,
//默认是最大充满、还是根据child显示最小大小
mainAxisSize: MainAxisSize.max,
//副轴方向,Column的横向、Row我的竖向
crossAxisAlignment :CrossAxisAlignment.center,

Expanded

在Column和Row中代表平均充满,当又两个存在的时候,默认平均充满,同事可以设置flex属性决定比例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Column(
    //主轴居中,即竖直向居中
    mainAxisAlignment: MainAxisAlignment.center,
    //大小按照最小显示
    mainAxisSize: MainAxisSize.min,
    crossAxisAlignment: CrossAxisAlignment.center,
    children: <Widget>[
        ///flex默认值为1
        Expanded(child: Center(child:Text("1111")),flex: 2,),
        Expanded(child: Text("222"),),
    ],
    )

ListView

  • childrenDelegate:自定义子模型时用到。

  • itemExtent:Item 的范围,scrollDirection 为 Axis.vertical 时限制高度,scrollDirection 为 Axis.horizontal 限制宽度。

  • cacheExtent:预加载的区域。

  • controller:滑动监听,值为一个 ScrollController 对象,这个属性应该可以用来做下拉刷新和上垃加载,后面详细研究。

  • padding:整个 ListView 的内间距。

  • physics:设置 ListView 如何响应用户的滑动行为,值为一个 ScrollPhysics 对象,它的实现类常用的有:

    • AlwaysScrollableScrollPhysics:总是可以滑动。
    • NeverScrollableScrollPhysics:禁止滚动。
    • BouncingScrollPhysics:内容超过一屏,上拉有回弹效果。
    • ClampingScrollPhysics:包裹内容,不会有回弹,感觉跟 AlwaysScrollableScrollPhysics 差不多。
  • primary:是否是与 PrimaryScrollController 关联的主滚动视图,若为 true 则 controller 必须为空。  

  • reverse:Item 的顺序是否反转,若为 true 则反转。

  • scrollDirection:ListView 的方向,为 Axis.vertical 表示纵向,为 Axis.horizontal 表示横向。

  • shrinkWrap:不太明白。

  • itemCount:子 Item 数量,只有使用 new ListView.builder() 和 new ListView.separated() 构造方法的时候才能指定,其中 new ListView.separated() 是必须指定。

 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
ListView.separated(
    itemBuilder: (context, index) {
    return Container(
        margin: EdgeInsets.only(left: 10.0,right: 10.0),
        padding: EdgeInsets.only(left: 10.0,right: 10.0),
        decoration: BoxDecoration(
            borderRadius: BorderRadius.all(Radius.circular(10.0),),
            color: Colors.white
        ),
        child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
            Container(
                child: Text(
                "这是一点描述",
                style: TextStyle(color: Colors.grey, fontSize: 14.0,),
                maxLines: 3,
                overflow: TextOverflow.ellipsis,
                ),
                margin: EdgeInsets.only(top: 6.0, bottom: 2.0),
                alignment: Alignment.topLeft,
            ),
            Padding(padding: EdgeInsets.all(10.0),),
            Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                _getBottomItem(Icons.star, "1000"),
                _getBottomItem(Icons.link, "1000"),
                _getBottomItem(Icons.subject, "1000"),
                ],
            )
            ],
        )
    );
    },
    itemCount: 20,
    separatorBuilder: (context,index){
    return Divider(color: Colors.transparent);
    },
),

GridView

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

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

示例1 用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
import 'package:flutter/material.dart';

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

示例二-用GridView.count

 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

Stack

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

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

示例

 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

Card

Card来自Material组件库,可包含一些数据,通常用ListTile来组装。Card只有一个子widget,可以是column、row、list、grid或其它组合widget。 默认情况下,Card把自己的尺寸缩小为0像素。可以用SizedBox来指定card的尺寸。 Flutter中的Card有圆角和阴影效果。修改elevation可改变阴影效果。 elevation取值范围,参考 Elevation and Shadows 示例 若设置范围外的值,阴影效果会消失。

 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);