在Flutter的世界里,UI控件就是所谓的widget,通过组合不同的widget来实现我们的用户交互界面。
widget分为两种:StatelessWidget和StatefulWidget,我们先说说这个的区别
StatelessWidget:称为无状态组件,内部不保存状态界面不会发生改变。
StatefulWidget:称为有状态组件,内部保存状态界面会发生改变。
在移动开发中,我们经常会跟按钮、文本输入框、图片等打交道,Flutter中也不例外,使用Flutter开发的App,界面上的每一个UI元素都是一个Widget,通过不同的Widget组合形成一整个页面。除了按钮、输入框、图片等Widget外,Flutter还给我们提供了很多功能强大,界面美观的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
| import 'package:flutter/material.dart';
void main() => runApp(new TuyaApp());
class TuyaApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new Scaffold( appBar: new AppBar( title: new Text('Tuya Flutter Demo') ), body: new Center( child: new Text('Hello Tuya'), ), ), ); } }
|
在上面的代码是一个最简单的Hello Tuya程序,TuyaApp是我们自定义的一个类,它继承自StatelessWidget,代表它是一个无状态的组件,UI不会发生改变。build方法是父类的一个方法,被TuyaApp类重写了,继承自StatelessWidget的类必须实现TuyaApp方法并返回一个Widget对象。所以在上面的代码中,MaterialApp也是一个Widget,如果你用AndroidStudio查看源码,会发现MaterialApp的参数home也是一个Widget对象,所以上面的Scaffold也是一个Widget。
Text组件
Text组件是非常常用的组件,任何需要显示文本的地方基本都会用到。通过查看Text类的源码,可以发现Text是一个无状态的组件,下面的代码演示了如何修改Text组件的字号、颜色,给字体加粗、设置下划线、设置斜体等:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp( title: "Text Demo", home: new Scaffold( appBar: new AppBar( title: new Text("Text Demo"), ), body: new Center( child: new Text( "Hello Flutter", style: new TextStyle( color: Colors.red, fontSize: 20.0, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, decoration: new TextDecoration.combine([TextDecoration.underline]) ), ), ), ), ));
|
运行效果如下:

TexField组件
TextFiled组件用于文本的输入,示例代码如下:
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
| import 'package:flutter/material.dart';
void main() => runApp(new TuyaApp()); class TuyaApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: "Test", home: new Scaffold( appBar: new AppBar( title: new Text("Test") ), body: new Padding( padding: const EdgeInsets.all(8.0), child: new TextField( maxLines: 8, maxLength: 30, decoration: new InputDecoration( hintText: "Input something...", border: new OutlineInputBorder( borderRadius: const BorderRadius.all(Radius.circular(1.0)) ) ), ) ) ), ); } }
|
运行界面如下图:

Flutter提供了几种类型的按钮组件:RaisedButton FloatingActionButton FlatButton IconButton PopupMenuButton,下面用一段代码说明这几种按钮的用法:
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
| import 'package:flutter/material.dart';
void main() => runApp(new TuyaApp());
enum WhyFarther { harder, smarter, selfStarter, tradingCharter }
class TuyaApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Test', home: new Scaffold( appBar: new AppBar( title: new Text('Button') ), body: new Column( children: <Widget>[ new RaisedButton( child: new Text("Raised Button"), onPressed: (){}, ), new FloatingActionButton( child: new Icon(Icons.add), onPressed: (){}, ), new FlatButton( onPressed: (){}, child: new Text("Flat Button") ), new IconButton( icon: new Icon(Icons.list), onPressed: (){} ), new PopupMenuButton<WhyFarther>( onSelected: (WhyFarther result) {}, itemBuilder: (BuildContext context) => <PopupMenuEntry<WhyFarther>>[ const PopupMenuItem<WhyFarther>( value: WhyFarther.harder, child: const Text('第一个选项'), ), const PopupMenuItem<WhyFarther>( value: WhyFarther.smarter, child: const Text('第二个选项'), ), const PopupMenuItem<WhyFarther>( value: WhyFarther.selfStarter, child: const Text('第三个选项'), ), const PopupMenuItem<WhyFarther>( value: WhyFarther.tradingCharter, child: const Text('第四个选项'), ), ], ) ], ) ) ); } }
|
运行效果如下图所示:

Dialog组件
Flutter提供了两种类型的对话框:SimpleDialog和AlertDialog。SimpleDialog是一个可以显示附加的提示或操作的简单对话框,AlertDialog则是一个会中断用户操作的对话框,需要用户确认的对话框,下面用代码来说明其用法:
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 70 71 72 73 74 75 76 77
| import 'package:flutter/material.dart';
void main() => runApp(new TuyaApp());
class TuyaApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Test', home: new Scaffold( appBar: new AppBar( title: new Text('Test') ),
body: new MySimpleDialogView(), ), ); } }
class MyAlertDialogView extends StatelessWidget { @override Widget build(BuildContext context) { return new RaisedButton( child: new Text('显示AlertDialog'), onPressed: () { showDialog<Null>( context: context, barrierDismissible: false, builder: (BuildContext context) { return new AlertDialog( title: new Text('提示'), content: new Text('微软重申Windows 7将在2020年1月到达支持终点,公司希望利用这个机会说服用户在最新更新发布之前升级到Windows 10。'), actions: <Widget>[ new FlatButton( child: new Text('明白了'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); }, ); } }
class MySimpleDialogView extends StatelessWidget { @override Widget build(BuildContext context) { return new RaisedButton( child: new Text('显示SimpleDialog'), onPressed: () { showDialog( context: context, builder: (BuildContext ctx) { return new SimpleDialog( title: new Text('这是SimpleDialog'), children: <Widget>[ new SimpleDialogOption( onPressed: () { Navigator.pop(context); }, child: const Text('确定'), ), new SimpleDialogOption( onPressed: () { Navigator.pop(context); }, child: const Text('取消'), ), ], ); } ); }, ); } }
|
上面的代码分别展示了SimpleDialog和AlertDialog的基本用法。需要注意的是,这里并没有直接将按钮和显示对话框的逻辑写到TuyaApp类中,而是分两个StatelessWidget来写的,如果你直接将按钮及显示对话框的逻辑写到TuyaApp的build方法里,是会报错的,具体报错信息为:
1 2
| Navigator operation requested with a context that does not include a Navigator. 复制代码
|
意思是导航操作需要一个不包含Navigator的上下文对象,而如果我们将showDialog的逻辑写到TuyaApp的build方法中时,使用的是MaterialApp的上下文对象,这个上下文对象是包含Navigator的,所以就会报错。
运行效果如下图:


Image组件
Image组件用于显示一张图片,可以加载本地(项目中或手机存储中)或网络图片。
加载本地图片
使用下面的方法加载一张项目中的图片:
1
| new Image.asset(path, width: 20.0, height: 20.0, fit: BoxFit.cover)
|
其中path是项目中的图片目录。
加载项目中的图片一定要注意编辑pubspec.yaml文件:
假设当前我们在跟lib/同级的目录下创建了images/目录,在images/目录下存放了若干图片供项目使用,那么一定要记得在项目根目录下(也是跟images/同级的目录)编辑pubspec.yaml文件,打开pubspec.yaml文件,默认情况下assets是被注释了的,这里我们要取消注释assets并添加images/目录下的每个图片的路径,如下图所示:
在上图中我们配置了文件路径images/a_dot_burr.png,所以可以用下面的代码来加载图片了:
1
| new Image.asset('images/a_dot_burr.png', width: 20.0, height: 20.0, fit: BoxFit.cover)
|
width和height是图片长宽,为double类型,如果你传整型20则会报错。 如果要加载手机存储中的图片,使用下面的方法:
1
| new Image.file(path, width: 20.0, height: 20.0, fit: BoxFit.cover)
|
fit属性指定了图片显示的不同方式,有如下几个值:
- contain:尽可能大,同时仍然包含图片完全在目标容器内。
- cover:尽可能小,同时仍然覆盖整个目标容器。
- fill:通过拉伸图片的长宽比填充目标容器。
- fitHeight:确保是否显示了图片的完整高度,而不管是否意图片高度溢出了目标容器。
- fitWidth:确保是否显示了图片的完整宽度,而不管是否图片高度溢出目标容器。
- none:对齐目标容器内的图片(默认情况下居中)并丢弃位于容器外的图片的任何部分。图片原始大小不会被调整。
- scaleDown:对齐目标容器内的图片(默认情况下居中),如果必要的话,对图片进行缩放,以确保图片适合容器。这与contain的情况相同,否则它与没有一样。
加载网络图片
加载网络图片使用下面的方法:
1
| new Image.network(imgUrl, width: 20.0, height: 20.0, fit: BoxFit.cover)
|
ListView组件
ListView组件用于显示一个列表,一个最简单的ListView可以用如下代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import 'package:flutter/material.dart';
void main() { List<Widget> items = new List(); for (var i = 0; i < 20; i++) { items.add(new Text("List Item $i")); } runApp(new MaterialApp( title: "Text Demo", home: new Scaffold( appBar: new AppBar( title: new Text("Text Demo"), ), body: new Center( child: new ListView(children: items) ), ), )); }
|
运行结果如下图所示:

这样的ListView显示不是我们需要的,太难看,每个item没有边距而且没有分割线,所以我们用下面的代码改造一下:
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
| import 'package:flutter/material.dart';
void main() { List<Widget> items = new List(); for (var i = 0; i < 20; i++) { var text = new Text("ListView Item $i"); items.add(new Padding( padding: const EdgeInsets.all(15.0), child: text )); } runApp(new MaterialApp( title: "ListView", home: new Scaffold( appBar: new AppBar( title: new Text("ListView"), ), body: new Center( child: new ListView.builder( itemCount: items.length * 2, itemBuilder: (context, index) { if (index.isOdd) { return new Divider(height: 1.0); } index = index ~/ 2; return items[index]; }, ) ) ), )); }
|

小结
关于Flutter的常用组件,我们就介绍到这,以上的示例代码可以直接运行在AndroidStudio或VSCode中看到效果。更多组件和用法可以在Flutter中文网中查看。