基础组件
文本
最简单的用法就是只提供文本内容就可以了。比如
Text("Hello world");
TextStyle
用于指定文本显示的样式如颜色、字体、粗细、背景等。
Text("Hello world",
style: TextStyle(
color: Colors.blue,
fontSize: 18.0,
height: 1.2,
fontFamily: "Courier",
background: new Paint()..color=Colors.yellow,
decoration:TextDecoration.underline,
decorationStyle: TextDecorationStyle.dashed
),
);
- 富文本
Text.rich(TextSpan(
children: [
TextSpan(
text: "Home: "
),
TextSpan(
text: "https://flutterchina.club",
style: TextStyle(
color: Colors.blue
),
recognizer: _tapRecognizer // 点击响应
),
]
));
按钮
-
RaisedButton
即"漂浮"按钮,它默认带有阴影和灰色背景。也就是常用的普通按钮
RaisedButton(
child: Text("normal"),
onPressed: () {},
);
-
FlatButton
即扁平按钮,默认背景透明并不带阴影。按下后,会有背景色
FlatButton(
child: Text("normal"),
onPressed: () {},
)
-
OutlineButton
默认有一个边框,不带阴影且背景透明。按下后,边框颜色会变亮、同时出现背景和阴影(较弱),
OutlineButton(
child: Text("normal"),
onPressed: () {},
)
-
IconButton
是一个可点击的Icon,不包括文字,默认没有背景,点击后会出现背景
IconButton(
icon: Icon(Icons.thumb_up),
onPressed: () {},
)
-
RaisedButton、FlatButton、OutlineButton
都有一个icon
构造函数,通过它可以轻松创建带图标的按钮
RaisedButton.icon(
icon: Icon(Icons.send),
label: Text("发送"),
onPressed: _onPressed,
),
OutlineButton.icon(
icon: Icon(Icons.add),
label: Text("添加"),
onPressed: _onPressed,
),
FlatButton.icon(
icon: Icon(Icons.info),
label: Text("详情"),
onPressed: _onPressed,
),
图片和ICON
- 本地图片
- 在工程根目录下创建一个
images
目录,并将图片avatar.png
拷贝到该目录。2.0x 3.0x
分别是2
倍图和3
倍图的目录,图片名字保持一致。
- 在
pubspec.yaml
中的flutter
部分添加资源信息
- 在代码中添加图片
Image.asset("images/avatar.png",
width: 100.0,
)
- 从网络加载图片
Image.network(
"https://avatars2.githubusercontent.com/u/20411648?s=460&v=4",
width: 100.0,
)
-
fit:
该属性用于在图片的显示空间和图片本身大小不同时指定图片的适应模式
-
Flutter
封装了IconData
和Icon
来专门显示字体图标
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.accessible,color: Colors.green,),
Icon(Icons.error,color: Colors.green,),
Icon(Icons.fingerprint,color: Colors.green,),
],
)
单选开关和复选框
Material
组件库中提供了Material
风格的单选开关Switch
和复选框Checkbox
,它们都是继承自StatelessWidget
,所以它们本身不会保存当前选择状态,因此它们的选中状态都是由父组件来管理的。当Switch
或Checkbox
被点击时,会触发它们的onChanged
回调,我们可以在此回调中处理选中状态改变逻辑。
import 'package:flutter/material.dart';
class SelectDemo extends StatefulWidget {
@override
State createState() {
return _SelectDemoState();
}
}
class _SelectDemoState extends State {
bool _switchSelected = true; //维护单选开关状态
bool _checkboxSelected = true; //维护复选框状态
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('单选和多选例子'),
),
body: Column(
children: [
Switch(
value: _switchSelected,//当前状态
onChanged:(value){
//重新构建页面
setState(() {
_switchSelected = value;
});
},
),
Checkbox(
value: _checkboxSelected,
activeColor: Colors.red, //选中时的颜色
onChanged:(value){
setState(() {
_checkboxSelected = value;
});
} ,
)
],
),
);
}
}
输入框及表单
TextField
用于文本输入,'controller':编辑框的控制器,通过它可以设置/获取编辑框的内容、选择编辑内容、监听编辑文本改变事件
InputDecoration
:用于控制TextField的外观显示,如提示文本、背景颜色、边框等keyboardType:
用于设置该输入框默认的键盘输入类型Flutter
提供了一个Form
组件,它可以对输入框进行分组,然后进行一些统一操作,如输入内容校验、输入框重置以及输入内容保存。为了方便使用,
Flutter
提供了一个TextFormField
组件,它继承自FormField
类,也是TextField
的一个包装类,所以除了FormField
定义的属性之外,它还包括TextField
的属性。
import 'package:flutter/material.dart';
class FormDemo extends StatefulWidget {
@override
State createState() {
return _FormDemoState();
}
}
class _FormDemoState extends State {
TextEditingController _unameController = TextEditingController();
TextEditingController _pwdController = TextEditingController();
@override
void initState() {
// TODO: implement initState
super.initState();
//监听输入改变
_unameController.addListener((){
print(_unameController.text);
});
//监听密码改变
_pwdController.addListener((){
print(_pwdController.text);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('输入框表单验证例子'),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
child: Form(
child: Column(
children: [
TextFormField(
autofocus: true,
controller: _unameController,
decoration: InputDecoration(
labelText: '用户名',
hintText: '用户名或邮箱',
icon: Icon(Icons.person),
),
validator: (v) {
return v.trim().length > 0 ? null : "用户名不能为空";
},
),
TextFormField(
obscureText: true,
controller: _pwdController,
decoration: InputDecoration(
labelText: '密码',
hintText: '您的登录密码',
icon: Icon(Icons.lock),
),
validator: (v) {
return v.trim().length > 5 ? null : "密码不能少于6位";
},
),
Padding(
padding: const EdgeInsets.only(top: 28),
child: Row(
children: [
Expanded(
child: Builder(
builder: (context) {
return RaisedButton(
padding: const EdgeInsets.all(15),
child: Text('登录'),
color: Theme.of(context).primaryColor,
textColor: Colors.white,
onPressed: () {
if(Form.of(context).validate()){
//验证通过提交数据
print('用户名:${_unameController.text}');
print('密码:${_pwdController.text}');
}
},
);
}
),
),
],
),
),
],
),
),
),
);
}
}
进度指示器
-
LinearProgressIndicator
是一个线性、条状的进度条
// 模糊进度条(会执行一个动画),蓝色条一直在移动
LinearProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
// 进度条是静止的,停在50%的位置
LinearProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
value: .5,
)
-
CircularProgressIndicator
是一个圆形进度条
// 模糊进度条(会执行一个旋转动画)
CircularProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
//进度条显示50%,会显示一个半圆
CircularProgressIndicator(
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
value: .5,
),
容器类组件
填充
-
Padding
可以给其子节点添加填充(留白),和边距效果类似。边距用EdgeInsets
来表示
Padding(
//上下左右各添加16像素补白
padding: EdgeInsets.all(16.0),
child: Column(
//显式指定对齐方式为左对齐,排除对齐干扰
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
//左边添加8像素补白
padding: const EdgeInsets.only(left: 8.0),
child: Text("Hello world"),
),
Padding(
//上下各添加8像素补白
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text("I am Jack"),
),
Padding(
// 分别指定四个方向的补白
padding: const EdgeInsets.fromLTRB(20.0,.0,20.0,20.0),
child: Text("Your friend"),
)
],
),
)
尺寸限制类容器
-
ConstrainedBox
用于对子组件添加额外的约束,比如,最大最小的宽或者高。
Widget redBox=DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
);
ConstrainedBox(
constraints: BoxConstraints(
minWidth: double.infinity, //宽度尽可能大
minHeight: 50.0 //最小高度为50像素
),
child: Container(
// 最小高度是50,所以这里的高度5不起作用。
height: 5.0,
child: redBox
),
)
-
SizedBox
用于给子元素指定固定的宽高
SizedBox(
width: 80.0,
height: 80.0,
child: redBox
)
-
UnconstrainedBox
不会对子组件产生任何限制,它允许其子组件按照其本身大小绘制。假如设置的宽高没有起效果,(像上面的高度5),那么很有可能是父组件做了限制。这个时候,就可以通过UnconstrainedBox
来消除这种影响。
AppBar(
title: Text(title),
actions: [
// 消除AppBar对子组件的尺寸限制
UnconstrainedBox(
child: SizedBox(
// 如果没有UnconstrainedBox,20 * 20的尺寸设置将无效
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation(Colors.white70),
),
),
)
],
)
装饰容器
DecoratedBox
可以在其子组件绘制前(或后)绘制一些装饰(Decoration)
,如背景、边框、渐变等。
DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(colors:[Colors.red,Colors.orange[700]]), //背景渐变
borderRadius: BorderRadius.circular(3.0), //3像素圆角
boxShadow: [ //阴影
BoxShadow(
color:Colors.black54,
offset: Offset(2.0,2.0),
blurRadius: 4.0
)
]
),
child: Padding(padding: EdgeInsets.symmetric(horizontal: 80.0, vertical: 18.0),
child: Text("Login", style: TextStyle(color: Colors.white),),
)
)
变换
-
Transform
可以实现平移,旋转,缩放,
import 'dart:math' as math;
DecoratedBox(
decoration:BoxDecoration(color: Colors.red),
child: Transform.rotate(
//旋转90度
angle:math.pi/2 ,
child: Text("Hello world"),
),
);
-
RotatedBox
也可以实现旋转,相比Transform.rotate
更加彻底。
Container(
margin: EdgeInsets.symmetric(vertical: 40),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.red,
),
child: RotatedBox(
// 旋转90度(1/4圈)
quarterTurns: 1,
child: Text('Hello world'),
),
),
),
容器
-
Containe
r是DecoratedBox、ConstrainedBox、Transform、Padding、Align
等组件组合的一个多功能容器,可以实现同时需要装饰、变换、限制的场景。
Container(
margin: EdgeInsets.only(top: 50.0, left: 120.0), //容器外填充
constraints: BoxConstraints.tightFor(width: 200.0, height: 150.0), //卡片大小
decoration: BoxDecoration(//背景装饰
gradient: RadialGradient( //背景径向渐变
colors: [Colors.red, Colors.orange],
center: Alignment.topLeft,
radius: .98
),
boxShadow: [ //卡片阴影
BoxShadow(
color: Colors.black54,
offset: Offset(2.0, 2.0),
blurRadius: 4.0
)
]
),
transform: Matrix4.rotationZ(.2), //卡片倾斜变换
alignment: Alignment.center, //卡片内文字居中
child: Text( //卡片文字
"5.20", style: TextStyle(color: Colors.white, fontSize: 40.0),
),
);
剪裁
比如圆形的头像之类的。
import 'package:flutter/material.dart';
class ClipTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 头像
Widget avatar = Image.asset("imgs/avatar.png", width: 60.0);
return Center(
child: Column(
children: [
avatar, //不剪裁
ClipOval(child: avatar), //剪裁为圆形
ClipRRect( //剪裁为圆角矩形
borderRadius: BorderRadius.circular(5.0),
child: avatar,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Align(
alignment: Alignment.topLeft,
widthFactor: .5,//宽度设为原来宽度一半,另一半会溢出
child: avatar,
),
Text("你好世界", style: TextStyle(color: Colors.green),)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ClipRect(//将溢出部分剪裁
child: Align(
alignment: Alignment.topLeft,
widthFactor: .5,//宽度设为原来宽度一半
child: avatar,
),
),
Text("你好世界",style: TextStyle(color: Colors.green))
],
),
],
),
);
}
}
布局类组件
线性布局
Row
和Column
来实现线性布局
Column(
//测试Row对齐方式,排除Column默认居中对齐的干扰
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(" hello world "),
Text(" I am Jack "),
],
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(" hello world "),
Text(" I am Jack "),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
textDirection: TextDirection.rtl,
children: [
Text(" hello world "),
Text(" I am Jack "),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
verticalDirection: VerticalDirection.up,
children: [
Text(" hello world ", style: TextStyle(fontSize: 30.0),),
Text(" I am Jack "),
],
),
],
);
弹性布局
弹性布局允许子组件按照一定比例来分配父容器空间。
//Flex的两个子widget按1:2来占据水平空间
Flex(
direction: Axis.horizontal,
children: [
Expanded(
flex: 1,
child: Container(
height: 30.0,
color: Colors.red,
),
),
Expanded(
flex: 2,
child: Container(
height: 30.0,
color: Colors.green,
),
),
],
),
流式布局
Row
和Colum
,如果子widget
超出屏幕范围,则会报溢出错误。Wrap
可以让溢出部分自动折行。
Wrap(
spacing: 8.0, // 主轴(水平)方向间距
runSpacing: 4.0, // 纵轴(垂直)方向间距
alignment: WrapAlignment.center, //沿主轴方向居中
children: [
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'),
),
],
)
层叠布局
层叠布局和Web
中的绝对定位、Android中
的Frame
布局是相似的,子组件可以根据距父容器四个角的位置来确定自身的位置。绝对定位允许子组件堆叠起来(按照代码中声明的顺序)。Flutter
中使用Stack
和Positioned
这两个组件来配合实现绝对定位。Stack
允许子组件堆叠,而Positioned
用于根据Stack
的四个角来确定子组件的位置。
//通过ConstrainedBox来确保Stack占满屏幕
ConstrainedBox(
constraints: BoxConstraints.expand(),
child: Stack(
alignment:Alignment.center , //指定未定位或部分定位widget的对齐方式
children: [
Container(child: Text("Hello world",style: TextStyle(color: Colors.white)),
color: Colors.red,
),
Positioned(
left: 18.0,
child: Text("I am Jack"),
),
Positioned(
top: 18.0,
child: Text("Your friend"),
)
],
),
);
对齐与相对定位
-
Align
组件可以调整子组件的位置,并且可以根据子组件的宽高来确定自身的的宽高
Container(
height: 120.0,
width: 120.0,
color: Colors.blue[50],
child: Align(
alignment: Alignment.topRight,
child: FlutterLogo(
size: 60,
),
),
)
-
Center
继承自Align
,它比Align只少了一个alignment
参数;
DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
child: Center(
child: Text("xxx"),
),
),
DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
child: Center(
widthFactor: 1,
heightFactor: 1,
child: Text("xxx"),
),
)
可滚动组件
SingleChildScrollView
SingleChildScrollView
类似于Android
中的ScrollView
,它只能接收一个子组件。
Scrollbar( // 显示进度条
child: SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Center(
child: Column(
//动态创建一个List
children: str.split("")
//每一个字母都用一个Text显示,字体为原来的两倍
.map((c) => Text(c, textScaleFactor: 2.0,))
.toList(),
),
),
),
)
ListView
- 默认构造函数有一个
children
参数,它接受一个Widget
列表(List)
。这种方式适合只有少量的子组件的情况。
ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(20.0),
children: [
const Text('I\'m dedicating every day to you'),
const Text('Domestic life was never quite my style'),
const Text('When you smile, you knock me out, I fall apart'),
const Text('And I thought I was so smart'),
],
)
-
ListView.builder
适合列表项比较多(或者无限)的情况
ListView.builder(
itemCount: 100,
itemExtent: 50.0, //强制高度为50.0
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
}
)
GridView
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //横轴三个子widget
childAspectRatio: 1.0 //宽高比为1时,子widget
),
children:[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast)
]
)
功能型组件
异步UI更新
FutureBuilder
会依赖一个Future
,它会根据所依赖的Future
的状态来动态构建自身。
Future mockNetworkData() async {
return Future.delayed(Duration(seconds: 2), () => "我是从互联网上获取的数据");
}
Widget build(BuildContext context) {
return Center(
child: FutureBuilder(
future: mockNetworkData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// 请求已结束
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
// 请求失败,显示错误
return Text("Error: ${snapshot.error}");
} else {
// 请求成功,显示数据
return Text("Contents: ${snapshot.data}");
}
} else {
// 请求未结束,显示loading
return CircularProgressIndicator();
}
},
),
);
}
对话框
//点击该按钮后弹出对话框
RaisedButton(
child: Text("对话框1"),
onPressed: () async {
//弹出对话框并等待其关闭
bool delete = await showDeleteConfirmDialog1();
if (delete == null) {
print("取消删除");
} else {
print("已确认删除");
//... 删除文件
}
},
),
// 弹出对话框
Future showDeleteConfirmDialog1() {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("提示"),
content: Text("您确定要删除当前文件吗?"),
actions: [
FlatButton(
child: Text("取消"),
onPressed: () => Navigator.of(context).pop(), // 关闭对话框
),
FlatButton(
child: Text("删除"),
onPressed: () {
//关闭对话框并返回true
Navigator.of(context).pop(true);
},
),
],
);
},
);
}
手势识别
class _ScaleTestRouteState extends State<_ScaleTestRoute> {
double _width = 200.0; //通过修改图片宽度来达到缩放效果
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
//指定宽度,高度自适应
child: Image.asset("./images/sea.png", width: _width),
onScaleUpdate: (ScaleUpdateDetails details) {
setState(() {
//缩放倍数在0.8到10倍之间
_width=200*details.scale.clamp(.8, 10.0);
});
},
),
);
}
}
指针事件
...
//定义一个状态,保存当前指针位置
PointerEvent _event;
...
Listener(
child: Container(
alignment: Alignment.center,
color: Colors.blue,
width: 300.0,
height: 150.0,
child: Text(_event?.toString()??"",style: TextStyle(color: Colors.white)),
),
onPointerDown: (PointerDownEvent event) => setState(()=>_event=event),
onPointerMove: (PointerMoveEvent event) => setState(()=>_event=event),
onPointerUp: (PointerUpEvent event) => setState(()=>_event=event),
)
实例
Demo例子