Flutter入门并开发天气预报APP(4)——基础Widget

到这章我们就差不多可以开始写天气预报了。首先我们来看一下一些基础简单的Widget。

基础组件

文本

Text用于显示简单的文本,包含一些控制文本显示的属性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Text(
    "1234",
),
Text(
    "1234",
    style: TextStyle(
        color: Colors.purple,
        fontSize: 32.0,
        fontWeight: FontWeight.bold,
    ),
),

Text示例

  • 需要显示的文本信息直接放到一个双引号里面就可以了;
  • textAlign:文本对齐方式;
  • maxLines:文本显示的最大行数;
  • overflow:指定多余文本的截断方式;
  • textScaleFactor:指定文本相对于当前字体大小的缩放因子;
  • TextStyle:设置显示文本的字体、颜色、粗细等样式:
    • height:指定行高,但是不是绝对值,而是一个因子,相当于fontsize * height
    • fontFamily:设置字体;
    • fontSize:设置字体大小

按钮

不同的组件库有不同的按钮,我们现在只拿Material组件库中的按钮举例。

RaisedButton

漂浮按钮,带有阴影和灰色背景。按下后阴影会变大。

1
2
3
4
RaisedButton(
  child: Text("1234"),
  onPressed: () {},
);

FlatButton

扁平化按钮,背景透明且不带阴影,按下后会有背景色。

1
2
3
4
FlatButton(
  child: Text("1234"),
  onPressed: () {},
)

IconButton

可点击的Icon,默认没有背景,按下后出现阴影

1
2
3
4
IconButton(
  icon: Icon(Icons.thumb_up),
  onPressed: () {},
)

-w88

图片

我们通过Image来显示图片,来源可以是asset、网络等位置。

从asset加载图片

  1. 现在项目根目录(也就是和android、ios、lib等目录同级)新建一个images目录,并把图片main.png拷进去;
  2. pubspec.yaml中的flutter部分添加一下内容: assets
  3. 加载该图片
1
2
3
Image(
  image: AssetImage("images/amoled.png"),
);

Image也提供了一个快速构造函数:

1
2
3
Image.asset(
  "images/amoled.png",
)

从网络加载图片

1
2
3
4
Image(
  image: NetworkImage(
      "https://s2.ax1x.com/2019/05/27/VZrQ3V.png"),
)

或者

1
2
3
Image.network(
  "https://s2.ax1x.com/2019/05/27/VZrQ3V.png",
)

参数

Image有一些基本参数

1
2
3
4
5
6
7
8
9
const Image({
  ...
  this.width, //图片的宽
  this.height, //图片高度
  this.fit,//缩放模式
  this.alignment = Alignment.center, //对齐方式
  this.repeat = ImageRepeat.noRepeat, //重复方式
  ...
})
  • widthheight:宽和高;
  • fit:缩放模式:
    • fill:拉伸图片知道填满;
    • cover:按原图长宽比放大图片来填满,多余的部分舍去;
    • contain:在保证图片长宽比不变的情况下尽可能去填满;
    • fitWidth:宽度会缩放到显示空间的宽度,高度会按比例缩放,如果有多余的部分会被舍去;
    • fitHeight:与fitWidth同理;
    • none:没有适应策略,图片多大就显示多大,如果图片原尺寸小于显示空间就只显示原尺寸;如果大于则舍弃多余部分只显示中间部分;
  • repeat:当图片小于显示空间时,会将图片重复显示。

布局组件

线性布局(Row、Column)

线性布局就相当于Android里面的LinearLayout,但是不同的是Flutter将竖直和水平布局单独拿了出来。

线性布局分为竖直布局Column和水平布局Row。他两属性都是一样的。 再说属性之前我们先熟悉两个概念:主轴和交叉轴。如果是Column,主轴是竖直轴,交叉轴是水平轴;如果是Row,主轴是水平轴,交叉轴是竖直轴。

1
2
3
4
5
6
7
Row({
  ...  
  MainAxisSize mainAxisSize = MainAxisSize.max,    
  MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
  CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
  List<Widget> children = const <Widget>[],
})
  • mainAxisSize:主轴占用空间。默认是MainAxisSize.max,是指占用全部主轴空间。如果设置为MainAxisSize.min,那就只占用所有子组件所需要的空间;
  • mainAxisAlignment:子Widget在主轴的对其方向;
  • crossAxisAlignment:子Widget在交叉轴的对其方向;
  • children:所有的子Widget。

弹性布局(Flex)

其实这个布局和线性布局很有渊源,为什么这么说呢,因为RowColumn都继承自它。 因此关于他和线性布局重复的地方我们现在就不再讲了,我们直说他“弹性”的部分。

Expanded

可以按比例“拉伸”RowColumnFlex子组件所占的空间。

1
2
3
4
const Expanded({
  int flex = 1, 
  @required Widget child,
})

flex为弹性系数,如果为0或者null,则child不会阔伸占用的控件。如果大于0,则会按照flex的比例来分隔主轴全部空闲空间。其实说白了,就和Android里面LinearLayoutweight一样的。但是我们还是来举个例子吧。

 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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
return Scaffold(
      appBar: new AppBar(
        title: Text("$_title"),
      ),
      body: Center(
        child: Flex(
          children: <Widget>[
            Flex(
              direction: Axis.horizontal,
              children: <Widget>[
                Expanded(
                  flex: 1,
                  child: Container(
                    color: Colors.blue,
                    child: Text("1234"),
                  ),
                ),
                Expanded(
                  flex: 2,
                  child: Container(
                    color: Colors.red,
                    child: Text("1234"),
                  ),
                ),
              ],
            ),
            Flex(
              direction: Axis.horizontal,
              children: <Widget>[
                Expanded(
                  flex: 3,
                  child: Container(
                    color: Colors.blue,
                    child: Text("1234"),
                  ),
                ),
                Expanded(
                  flex: 1,
                  child: Container(
                    color: Colors.red,
                    child: Text("1234"),
                  ),
                ),
              ],
            ),
            Flex(
              direction: Axis.horizontal,
              children: <Widget>[
                Expanded(
                  flex: 1,
                  child: Container(
                    color: Colors.blue,
                    child: Text("1234"),
                  ),
                ),
                Expanded(
                  flex: 1,
                  child: Container(
                    color: Colors.red,
                    child: Text("1234"),
                  ),
                ),
              ],
            ),
          ],
          direction: Axis.vertical,
        ),
      ),
);

Flex示例

流式布局(Wrap、Flow)

如果使用线性布局的话,当需要显示的内容超出屏幕边界的时候就会报错。 超出屏幕边界

为了避免这种情况,我们就可以使用流式布局。当需要显示的内容超出屏幕边界的时候,就自动折行来继续显示。

Flutter中通过WrapFlow来实现流式布局。

Wrap

我们来看下Wrap主要的一些参数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Wrap({
  ...
  this.direction = Axis.horizontal,
  this.alignment = WrapAlignment.start,
  this.spacing = 0.0,
  this.runAlignment = WrapAlignment.start,
  this.runSpacing = 0.0,
  this.crossAxisAlignment = WrapCrossAlignment.start,
  this.textDirection,
  this.verticalDirection = VerticalDirection.down,
  List<Widget> children = const <Widget>[],
})

其中很多参数RowColumn中都有,就不再介绍了,主要介绍点不同的:

  • spacing:主轴方向子widget的间距
  • runSpacing:纵轴方向的间距
  • runAlignment:纵轴方向的对齐方式

下面有一个示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Wrap(
  spacing: 8.0, // 主轴(水平)方向间距
  runSpacing: 4.0, // 纵轴(垂直)方向间距
  alignment: WrapAlignment.center, //沿主轴方向居中
  children: <Widget>[
    new Chip(
      avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('A')),
      label: new Text('Hamilton'),
    ),
    new Chip(
      avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('M')),
      label: new Text('Lafayette'),
    ),
    new Chip(
      avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('H')),
      label: new Text('Mulligan'),
    ),
    new Chip(
      avatar: new CircleAvatar(backgroundColor: Colors.blue, child: Text('J')),
      label: new Text('Laurens'),
    ),
  ],
)

Wrap示例

Flow

Flow较复杂,需要自己实现子Widget的位置转换,一般不推荐使用Flow。但是如果需要自定义布局策略,或者对性能要求较高,这个时候就得用Flow了。

但是由于太过于复杂,我也没咋用过,我就不在这讲了😝,大家有需要的可以百度,或者看我推荐的这篇:4.4 流式布局-《Flutter实战》

层叠布局(Stack)

这个布局类似于Android中的Frame,允许在父布局的任意地方放置布局。

1
2
3
4
5
6
7
Stack({
  this.alignment = AlignmentDirectional.topStart,
  this.textDirection,
  this.fit = StackFit.loose,
  this.overflow = Overflow.clip,
  List<Widget> children = const <Widget>[],
})
  • alignment:决定如何去对齐;
  • textDirection:确定alignment的参考系;
  • fit:没有定位的子Widget如何去适应Stack的大小;
  • overflow:决定超出Stack显示空间的子Widget如何去显示,如果Overflow.clip,则超出部分会隐藏,Overflow.visible则不会。

对齐与相对定位(Align)

Align可以调整子Widget的位置,并且可以根据子Widget的宽高来确定自身的宽高。

1
2
3
4
5
6
7
Align({
  Key key,
  this.alignment = Alignment.center,
  this.widthFactor,
  this.heightFactor,
  Widget child,
})
  • alignment:代表子Widget在父Widget的起始位置;
  • widthFactor和heightFactor确定Align本身的宽高。

来看一个简单的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Container(
  height: 120.0,
  width: 120.0,
  color: Colors.blue[50],
  child: Align(
    alignment: Alignment.topRight,
    child: FlutterLogo(
      size: 60,
    ),
  ),
)

运行结果: -w100

3. 容器类组件

填充(Padding)

用过Android的同学一定熟悉,Padding就是负责留白的嘛。 但是跟Android里面不同的是,在Android里面我们一般是在一个View里面添加Padding,但是在Flutter里面,Padding直接变成了一个Widget、一个布局。

使用方法:

1
2
3
4
Padding ({
  EdgeInsetsGeometry padding,
  Widget child,
})

对于EdgeInsetsGeometry我们一般使用EdgeInsets类。

EdgeInsets

EdgeInsets类提供了几个便捷的方法:

  • fromLTRB(double left, double top, double right, double bottom):分别指定四个方向的填充;
  • all(double value) : 所有方向均使用相同数值的填充;
  • only({left, top, right ,bottom }):可以设置具体某个方向的填充(可以同时指定多个方向);
  • symmetric({ vertical, horizontal }):用于设置对称方向的填充,vertical指top和bottom,horizontal指left和right;

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
body: Column(
  children: <Widget>[
    Padding(
      padding: EdgeInsets.only(left: 12.0, bottom: 10.0),
      child: Text("1234"),
    ),
    Padding(
      padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 10.0),
      child: Text("1234"),
    ),
    Padding(
      padding: EdgeInsets.symmetric(vertical: 10.0),
      child: Text("1234"),
    ),
  ],
),

运行结果: Padding示例