Flutter 学习笔记-基础篇
如果你要获取与该笔记配套的源码,请点击这里。
一、常用命令及快捷键
1. 常用命令
flutter doctor //Flutter自检命令
flutter run //运行项目到设备上
2. 常用快捷键
r 键:热加载(快速部署)。
p 键:显示网格,用于开发时调试UI。
o 键:切换Android和iOS的预览模式。
q 键:退出调试预览模式。
以上几个按键需要已经执行过flutter run
之后才可以在终端中使用。
二、基本Widget组件
Flutter中一切皆组件(Widget)。所有的自定义组件、系统组件、自定义页面等其实都是Widget的子类。每个widget的构造函数都有一个key
参数,这个参数的作用是什么呢?Key用于在widget的位置改变时保留其状态。比如,保留用户的滑动位置,或者在保留widget状态的情况下修改一个widget集合,如Row、Column等,这一篇博客详细d 讲解了Widget中的key。
1. 有状态无状态组件。
Flutter中如果要自定组件,一般都继承自有状态组件或无状态组件(小到组件大到页面),如果一个页面加载出来之后就不会再改变了那么就继承自StatelessWidget,如果加载之后还需要根据数据的变化而变化,那么就需要继承自StatefulWidget。
1). StatelessWidget 无状态组件。
StatelessWidget组件是一个抽象类,继承该组件必须要实现Widget build(BuildContext context)
方法。
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return Text("Hello Flutter!");
}
}
2). StatefulWidget 有状态组件。
StatefulWidget是一个抽象组件,继承该组件必须要实现State<StatefulWidget> createState()
方法。该方法需要返回一个State类的实例。而State是一个抽象类,要继承State需要实现Widget build(BuildContext context)
抽象方法,并制定泛型为拥有该State的Widget。
如果要实现改变状态,需要使用State类中的setState方法。下面的栗子是点击按钮后按钮上面的数字动态加1,具体代码如下:
class TestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
child: Custom(),
);
}
}
class Custom extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return _CustomState();
}
}
class _CustomState extends State<Custom>{
var myNumber = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text("$myNumber"),
SizedBox(height: 20),
RaisedButton(
onPressed: () {
setState(() {
myNumber++;
});
},
child: Text('增加'),
)
],
);
}
}
2. 装饰组件
1). MaterialApp
MaterialApp是一个方便的Widget,他封装了应用程序实现 Material Design 所需要的一些Widget。一般作为一个页面的最顶层的Widget使用。
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
home | Widget | 是 | -- | 页面的具体内容组件。 |
title | String | 是 | '' | 页面标题。 |
color | Color | 是 | -- | 页面颜色。 |
theme | ThemeData | 是 | -- | 页面主题。 |
routes | Map<String, WidgetBuilder> | 是 | const <String, WidgetBuilder>{} | 路由。 |
2). Scaffold
Scaffold 是 Material Design 布局结构的基本实现。此类提供了用于显示 drawer、snackbar 和底部的 sheet 的参数。
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
appBar | Widget | 是 | -- | 导航栏 |
body | Widget | 是 | -- | |
drawer | Widget | 是 | -- | |
bottomNavigationBar | Widget | 是 | -- |
3). InkWell & GestureDetector 手势组件
在Flutter中并不是所有的Widget都支持点击事件,但是如果我们想给某些我们自己的Widget设置点击事件时应该怎么办呢(例如我们列表中的item)?这时我们只需要使用InkWell将我们要设置点击事件的Widget包裹起来就可以了。
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
child | Widget | 是 | -- | 子组件 |
onTap | GestureTapCallback | 是 | -- | 单击事件回调 |
onDoubleTap | GestureTapCallback | 是 | -- | 双击事件回调 |
onLongPress | GestureLongPressCallback | 是 | -- | 长按事件回调 |
GestureDetector和InkWell用法基本一致,只是GestureDetector比InkWell多了更多回调设置,可以监听更多的事件。
3. Container 容器组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
constraints | BoxConstraints | 是 | -- | 用不约束宽高(最大宽高、最小宽高等) |
alignment | AlignmentGeometry | 是 | -- | 对齐方式 |
decoration | Decoration | 是 | -- | 设置边框装饰 |
padding | EdgeInsetsGeometry | 是 | -- | 内边距 |
margin | EdgeInsetsGeometry | 是 | -- | 外边距 |
transform | Matrix4 | 是 | -- | 旋转、平移 |
4. Text 文本组件。
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
data | String | 否 | -- | 用于设置现在界面上的文字 |
textAlign | TextAlign | 是 | -- | center:居中 / left:居左 / right:居右 / justfy:两端对齐 |
textDirection | TextDirection | 是 | -- | 文本方向,ltr:从左至右 rtl:从右至左 |
overflow | TextOverflow | 是 | -- | 文字超出屏幕后的处理方式,clip:裁剪 fade:渐隐 ellipsis:省略号 |
style | TextStyle | 是 | -- | 用于设置文字的样式 |
maxLines | int | 是 | -- | 用于设置文字的最大显示行数 |
textScaleFactor | double | 是 | -- | 字体显示倍率 |
5. ListView 列表组件。
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
itemCount | Int | 必填 | -- | 设置列表一共有多少个条目 |
itemBuilder | Widget Function(BuildContext context, int index) |
必填 | -- | 用于构建item(cell)视图的回调方法。 |
scrollDirection | Axis | 是 | Axis.vertical | 滚动方向:horizontal-横向 vertical纵向。 |
reverse | bool | 是 | false | 是否反向排列。 |
keyboardDismissBehavior | ScrollViewKeyboardDismissBehavior | 是 | ScrollViewKeyboardDismissBehavior.manual | 滚动时键盘的处理方式。 |
- 静态列表
@override
Widget build(BuildContext context) {
return ListView(
children: [
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目'),
],
);
}
- 动态列表
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 5,
itemBuilder: (context, index){
return ListTile(
leading: data[index].avatarUrl,
title: data[index].name,
subtitle: data[index].desc,
);
}
);
}
-
上拉加载更多
上拉加载更多有两种实现方式:
-
可以在
itemBuilder
回调中处理,判断当前index的数值,当构建最后一个条目时触发加载更多。@override Widget build(BuildContext context) { return ListView.builder( itemCount: this._data.length, itemBuilder: (context, index){ if (index == this._data.length) { onLoadMore(); //触发loadMore() } return ListTile( leading: this._data[index].avatarUrl, title: this._data[index].name, subtitle: this._data[index].desc, ); } ); }
-
可以利用ListView的controller属性监听列表的滚动。具体有以下几个步骤:
-
声明一个ScrollController变量
ScrollController _scrollController = ScrollController();
-
在initState生命周期中设置监听并判断滚动距离。
@override void initState() { super.initState(); this._scrollController.addListener(() { if(this._scrollController.position.pixels > this._scrollController.position.maxScrollExtent - 50) { onLoadMore(); //当滚动接近底部时触发加载更多。 } }); }
-
为ListView增加controller属性。
ListView.builder( controller: _scrollController, itemCount: this._data.length, itemBuilder: (context, index){ return ListTile( leading: this._data[index].avatarUrl, title: this._data[index].name, subtitle: this._data[index].desc, ); } );
到此上拉加载更多就已经实现了,但是这里还有问题。那就是如果我们已经加载完成了最后一页,我们不断的上拉会不断的请求接口,尽管接口会返回空的数据。所以我们还需要对此做进一步处理。
-
-
6. RefreshIndicator下拉刷新组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
child | Widget | 必填 | -- | 用来绘制内容视图,通常应该是ListView。 |
onRefresh | Future<void> Function() | 必填 | -- | 下拉刷新的监听 |
color | Color | 是 | 主题色 | 设置颜色 |
displacement | double | 是 | 40.0 | 设置下拉多少距离触发下拉刷新 |
RefreshIndicator(
child: ListView.builder(
itemCount: _dataList.length,
itemBuilder: _createItemCell
),
onRefresh: () => Future.delayed(Duration(), () => onRefresh()),
);
7. GridView
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
crossAxisSpacing | double | 是 | -- | 水平间距 |
mainAxisSpacing | double | 是 | -- | 垂直间距 |
scrollDirection | Axis | 是 | Axis.vertical | 滚动方向:horizontal-横向 vertical纵向。 |
reverse | bool | 是 | false | 是否反向排列。 |
childAspecet | double | 是 | -- | item的宽高比例。 |
crossAxisCount | int | 是 | -- | 设置列数 |
gridDelegate | SliverGridDelegate | 必填 | -- | builder专用。用于设置间距列数等。 |
itemBuilder | Widget Function(BuildContext context, int index) |
必填 | -- | builder专用。用于构建item(cell)视图的回调方法。 |
静态列表
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 2,
children: [
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目'),
Text('我是一个条目')
],
);
}
动态列表
@override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: 600,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3
),
itemBuilder: (context, position){
return Text('我是第$position个条目');
}
);
}
8. Padding 内边距组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
padding | EdgeInsetsGeometry | 必填 | -- | 用于设置内边距,其实是设置child的外边距。 |
child | Widget | 是 | -- | 子元素。 |
9. Row 水平布局组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
mainAxisAlignment | MainAxisAlignment | 是 | MainAxisAlignment.start | 主轴的排序方式 |
crossAxisAlignment | CrossAxisAlignment | 是 | CrossAxisAlignment.center | 次轴的排序方式 |
children | List<Widget> | 是 | const <Widget>[] | 子组件 |
10. Column 垂直布局组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
mainAxisAlignment | MainAxisAlignment | 是 | MainAxisAlignment.start | 主轴的排序方式 |
crossAxisAlignment | CrossAxisAlignment | 是 | CrossAxisAlignment.center | 次轴的排序方式 |
children | List<Widget> | 是 | const <Widget>[] | 子组件 |
11 Expanded 组件,类似Web中的Flex布局。
Expanded组件作为Column或Row组件的子组件使用。可设置当前组件占用父组件的比例。
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
flex | Int | 是 | 1 | 占父组件的比例(权重) |
child | Widget | 必填 | -- | 子组件 |
12. SizedBox 占位组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
width | double | 是 | -- | 宽度 |
height | double | 是 | -- | 高度 |
child | Widget | 必填 | -- | 子组件 |
13. Stack 层叠组件
Stack组件可以单独使用,也可配合 Align 和 Positioned 实现定位布局。其实Stack有点类似于Android原生的帧布局。
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
alignment | AlignmentGeometry | 是 | AlignmentDirectional.topStart | 所有子组件的显示位置 |
children | List<Widget> | 是 | const <Widget>[] | 子组件 |
overflow | Overflow | 是 | Overflow.clip | 子组件溢出后的处理方式 |
Stack(
alignment: Alignment.center,
children: [
Align(
alignment: Alignment.topCenter,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
Container(
width: 100,
height: 100,
color: Colors.orange,
),
Positioned(
bottom: 10,
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
)
],
);
14. Align
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
alignment | AlignmentGeometry | 是 | Alignment.center | 所有子组件的显示位置 |
child | Widget | 是 | -- | 子组件 |
15. Positioned
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
top | double | 是 | -- | 距顶部的距离 |
bottom | double | 是 | -- | 距底部的距离 |
left | double | 是 | -- | 距左边的距离 |
right | double | 是 | -- | 距右边的距离 |
child | Widget | 是 | -- | 子组件 |
width | double | 是 | -- | 宽度 |
height | double | 是 | -- | 高度 |
16. AspectRatio
作用是可以设置子组件的宽高比。
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
aspectRatio | double | 是 | -- | 设置宽高比例 |
child | Widget | 是 | -- | 子组件 |
17. Card
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
margin | EdgeInsetsGeometry | 是 | -- | 外边距 |
shape | ShapeBorder | 是 | -- | 边框及阴影效果,还可以使用RoundedRectangleBorder对其设置圆角 |
clipBehavior | Clip | 是 | Clip.none | 对子组件的裁剪方式 |
elevation | doublle | 是 | 1.0 | 阴影高度 |
child | Widget | 是 | -- | 子组件 |
18. CircleAvatar 原型头像组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
backgroundImage | ImageProvider | 是 | -- | 设置头像 |
onBackgroundImageError | Function(dynamic exception, StackTrace stackTrace) | 是 | -- | 图片加载失败的回调 |
19. Wrap组件(流式布局组件)
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
spacing | double | 是 | 0.0 | 主轴的子组件之间的间隔 |
runSpacing | double | 是 | 0.0 | 次轴的子组件之间的间隔 |
direction | Axis | 是 | Axis.horizontal | 子组件的排列方向,horizontal横向,vertical纵向。 |
alignment | WrapAlignment | 是 | WrapAlignment.start | 主轴子组件的对齐方式。 |
runAlignment | WrapAlignment | 是 | WrapAlignment.start | 次轴子组件的对齐方式。 |
textDirection | TextDirection | 是 | -- | 文本的排列方向。 |
20. RaisedButton & MaterialButton & FlatButton & OutlineButton & IconButton & FloatingActiongButton 按钮组件。
Flutter中给我们预先定义好了一些按钮控件给我们用,常用的按钮如下
-
RaisedButton :凸起的按钮,其实就是Android中的Material Design风格的Button ,继承自MaterialButton
如果要让RaisedButton支持图标,那么可以使用
RaisedButton.icon()
。 FlatButton :扁平化的按钮,继承自MaterialButton
OutlineButton :带边框的按钮,继承自MaterialButton
IconButton :图标按钮,继承自StatelessWidget
FloatingActionButton : 浮动按钮,继承自StatelessWidget
按钮通常情况下是不能直接设置宽高的,如果要设置宽高可是在外层包一个Container组件。也可以在外层包一个Expanded组件使其可以自适应宽度。
常用属性如下
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
onPressed | VoidCallback | 必传 | -- | 按下按钮时触发的回调方法,传null表示按钮禁用,会显示禁用相关样式。 |
child | Widget | 是 | -- | 子组件,不传就无法显示内容。 |
textColor | Color | 是 | -- | 文本颜色 |
color | Color | 是 | -- | 按钮的颜色 |
disabledColor | Color | 是 | -- | 按钮禁用时的颜色 |
disabledTextColor | Color | 是 | -- | 按钮禁用时的文本颜色 |
splashColor | Color | 是 | -- | 点击按钮时水波纹的颜色 |
highlightColor | Color | 是 | -- | 点击(长按)按钮后按钮的颜色 |
elevation | double | 是 | -- | 阴影的范围,值越大阴影范围越大 |
padding | EdgeInsetsGeometry | 是 | -- | 内边距 |
shape | ShapeBorder | 是 | -- | 设置按钮的形状 |
minWidth | double | 是 | -- | 最小宽度 |
height | double | 是 | -- | 高度 |
materialTapTargetSize | MaterialTapTargetSize | 是 | MaterialTapTargetSize.padded | 由于按钮按下后通常会有阴影效果,所以按钮默认都会有边距,如果不希望有边距可以通过该属性设置,MaterialTapTargetSize.padded:有边距。MaterialTapTargetSize.shrinkWrap:无边距。 |
21. Chip 碎片组件
该组件一般用作标签。
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
avatar | Widget | 是 | -- | 一般用来设置左边的图标。 |
label | Widget | 必填 | -- | 一般用来设置文字。 |
deleteIcon | Widget | 是 | -- | 删除图标,如果设置了onDeleted回调,即使该参数不设置也会显示默认图标。 |
deleteIconColor | Color | 是 | -- | 删除图标的颜色。 |
deleteButtonTooltipMessage | String | 是 | delete | 删除图标被按压时的提示文字。 |
shape | ShapeBorder | 是 | 默认为两边是半圆的形状 | 设置背景形状。 |
backgroundColor | Color | 是 | -- | 背景颜色。 |
materialTapTargetSize | MaterialTapTargetSize | 是 | MaterialTapTargetSize.padded | 由于按钮按下后通常会有阴影效果,所以按钮默认都会有边距,如果不希望有边距可以通过该属性设置,MaterialTapTargetSize.padded:有边距。MaterialTapTargetSize.shrinkWrap:无边距。 |
22. BottomNavigationBar 底部导航条组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
currentIndex | int | 是 | -- | 当前选中的Tab的索引。 |
items | List<BottomNavigationBarItem> | 必填 | -- | 设置当前有多少个Item。 |
iconSize | double | 是 | 24 | 图标大小 |
fixedColor | Color | 是 | -- | 选中后图标及字体的颜色。该属性通常不需要设置,会默认使用主体颜色。 |
type | BottomNavigationBarType | 是 | -- | BottomNavigationBarType.fixed : 自动将所有item都显示到屏幕上。 |
23. AppBar 导航栏组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
title | Widget | 是 | -- | 标题 |
centerTitle | bool | 是 | false | 标题是否居中显示 |
actions | List<Widget> | 是 | -- | 右边菜单 |
leading | Widget | 是 | -- | 左边的导航图标 |
24. DefaultTabController 组件(实现类似于Android中的TabLayout效果)
要实现类似Android中的LabLayout效果还需要结合另外两个组件TabBar
和TabBarView
一起使用,以下是这三个组件的具体参数介绍以及代码示例:
-
DefaultTabController
参数 类型 可选 默认值 说明 length int 必填 -- 设置有多少个tab。 initialIndex int 是 0 默认选中第几个tab,索引从0开始。 child Widget 必填 -- child通常是一个Scaffold组件,但是需要有特俗的写法。下面会给出栗子。 - TabBar
参数 类型 可选 默认值 说明 isScrollable bool 是 false 设置是否开启滚动,tab过多时建议开启,否则无法显示。 tabs List<Widget> 必填 -- 设置tab,具体有多少个tab由集合的长度决定。 controller TabController 是 -- 用于自己定义控制器。 indicatorColor Color 是 -- 设置指示器的颜色,默认会根据主题色做出调整。 indicatorWeight double 是 2.0 设置指示器的高度。 indicatorPadding EdgeInsetsGeometry 是 EdgeInsets.zero 底部指示器的padding, indicator Decoration 是 -- 设置Tab的样式,例如边框等。 indicatorSize TabBarIndicatorSize 是 -- 指示器大小的计算方式,TabBarIndicatorSize.label:和文字等宽,TabBarIndicatorSize.tab:和tab等宽。 labelColor Color 是 -- 统一设置标签字体颜色,默认与指示器颜色一致。 lablStyle TextStyle 是 -- 统一设置选中时标签字体样式。 unselectedLabelColor Color 是 -- 设置未选中时标签字体颜色,默认与指示器颜色一致。 unselectedLabelStyle TextStyle 是 -- 设置未选中时标签字体样式。 import 'package:flutter/material.dart'; class MovieDetailPage extends StatelessWidget { final data; List<String> get _movies { return data['movies']; } int get _index { return data['index']; } MovieDetailPage(this.data); @override Widget build(BuildContext context) { return DefaultTabController( // 1.要实现Android中的TabLayout需要使用DefaultTabController组件。 initialIndex: this._index, length: _movies.length, child: Scaffold( // 2.使用Scaffold作为DefaultTabController组件的child。 appBar: AppBar( // 3.设置appBar。 title: Text('电影专区'), centerTitle: true, bottom: TabBar( // 4.设置bottom属性为TabBar组件。 isScrollable: true, // 5.如果tab过多时需要将isScrollable属性设置为true,否者无法显示。默认为false。 tabs: _movies.map((movie) => Tab(text: movie)).toList() // 6.设置tab的样式,建议使用Tab组件。 ), ), body: TabBarView( // 7.设置body属性为TabBarView组件。 children: _movies.map((movie) { // 8.为TabBarView设置每个页面的具体内容(绘制每个页面)。 return Align( alignment: Alignment.center, child: Text('电影《$movie》的详情页面。')); }).toList(), ), ), ); } } //调用 Navigator.push(context, MaterialPageRoute(builder: (context) { return MovieDetailPage({ 'index': 0, 'movies': [ '天龙八部', '别拿村长不当干部', '长征', '白衣校花大长腿', '死侍', 'X战警', '007大破天幕杀机', '信条', '射雕英雄传', '新白娘子传奇', '三体', '鹿鼎记', '我和僵尸有个约会', '奇异博士', '蜘蛛侠', '复仇者联盟-无限战争', '复仇者联盟-逆转无限' ] }); }));
他还有一种高级用法,可以自己监听生命周期,并监听滚动事件等。具体用法如下:
import 'package:flutter/material.dart'; class SuperiorTabControllerPage extends StatefulWidget{ @override State<StatefulWidget> createState() { return _SuperiorTabControllerPageState(); } } class _SuperiorTabControllerPageState extends State<SuperiorTabControllerPage> with SingleTickerProviderStateMixin{ TabController _tabController; @override void initState() { super.initState(); _tabController = TabController( length: 2, vsync: this //1.固定写法 ); _tabController.addListener(() { print(this._tabController.index); }); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('TabController的高级用法'), bottom: TabBar( controller: this._tabController, //2.TabBar必须设置自己定义的TabController indicatorSize: TabBarIndicatorSize.label, tabs: [ Tab(text: '科幻'), Tab(text: '悬疑') ], ), ), body: TabBarView( controller: this._tabController, //3.TabBarView必须设置自己定义的TabController children: [ Center( child: Text('科幻'), ), Center( child: Text('悬疑'), ), ], ), ); } } //调用 Navigator.push(context, MaterialPageRoute( builder: (context) => SuperiorTabControllerPage() ))
25. Divider 线条组件
该组件通常用于在一面中绘制一条线。
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
height | double | 是 | -- | 线条的高度 |
color | Color | 是 | -- | 线条的颜色 |
26. ListTile 条目组件
该组件可以快速绘制类似于个人中心页面中的条目,或则手机设置页面中的条目。
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
leading | Widget | 是 | -- | 通常用于设置左侧的图标。 |
title | Widget | 是 | -- | 通常用于设置标题。 |
subtitle | Widget | 是 | -- | 通常用于设置子标题。 |
onTap | GestureTapCallback | 是 | -- | 用于监听点击事件。 |
27. Drawer 抽屉组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
elevation | double | 是 | 16.0 | 设置阴影高度 |
child | Widget | 必填 | -- | 绘制抽屉中的内容 |
在使用Drawer组件的时候我们通常要为抽屉设置头,否则会有点难看。如果要设置头就需要用到一下两个组件:
-
DrawerHeader
参数 类型 可选 默认值 说明 decoration Decoration 是 -- 用于设置样式,通常使用BoxDecoration。 margin EdgeInsetsGeometry 是 -- 设置外边距 padding EdgeInsetsGeometry 是 -- 设置内边距 child Widget 必填 -- 绘制内容视图 - UserAccountsDrawerHeader
参数 类型 可选 默认值 说明 accountName Widget 必填 -- 设置用户名 accountEmail Widget 必填 -- 设置用户邮箱 currentAccountPicture Widget 是 -- 设置当前用户头像 onDetailsPressed VoidCallback 是 -- 用户信息被点击后的监听。 arrowColor Color 是 -- 当设置了onDetailsPressed后右侧会出现一个小的三角箭头,该参数用于设置箭头颜色。 otherAccountsPictures List<Widget> 是 -- 用于设置其他用户头像,设置后会在当前头像的右侧出现小的头像。 如果需要用代码关闭抽屉可以使用下面的方式:
Navigator.pop(context)
28. TextField 文本框组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
decoration | InputDecoration | 是 | InputDecoration() | 设置边框、hint、lable、图标等 |
obscureText | bool | 是 | false | 是否隐藏明文显示内容(密码模式)。 |
obscuringCharacter | String | 是 | -- | 当obscureText为true时,用于设置显示的文字。 |
maxLines | int | 是 | 1 | 最大行数,当设置大于1时为多行输入模式。 |
minLines | int | 是 | -- | 最小输入行数,主要是用来设置最小高度。 |
controller | TextEditingController | 是 | -- | 用于设置默认显示内容以及获取编辑框中的内容。 |
onChanged | ValueChanged<String> | 是 | -- | 监听内容的改变。 |
onEditingComplete | VoidCallback | 是 | -- | 监听焦点的丢失(编辑完成) |
29. CheckBox 多选框组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
value | bool | 必填 | -- | 设置是否选中 |
onChanged | ValueChanged<bool> | 必填 | -- | 监听选中状态的改变 |
30. CheckboxListTile组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
value | bool | 必填 | -- | 设置是否选中 |
onChanged | ValueChanged<bool> | 必填 | -- | 监听选中状态的改变 |
title | Widget | 是 | -- | 通常用于设置标题。 |
subtitle | Widget | 是 | -- | 通常用于设置子标题。 |
secondary | Widget | 是 | -- | 配置图标或者图片。 |
selected | bool | 是 | false | 选中的时候文字是否跟着改变。 |
31. Radio 单选框组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
value | T | 必填 | -- | 设置当前单选框所代表的值。 |
onChanged | ValueChanged<T> | 必填 | -- | 监听选中状态的改变,并将当前单选框所代表的值(T)回传。 |
groupValue | T | 是 | -- | 为当前单选框分组。 |
32. RadioListTile组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
value | T | 必填 | -- | 设置当前单选框所代表的值。 |
onChanged | ValueChanged<T> | 必填 | -- | 监听选中状态的改变,并将当前单选框所代表的值(T)回传。 |
title | Widget | 是 | -- | 通常用于设置标题。 |
subtitle | Widget | 是 | -- | 通常用于设置子标题。 |
secondary | Widget | 是 | -- | 配置图标或者图片。 |
selected | bool | 是 | false | 选中的时候文字是否跟着改变。 |
groupValue | T | 是 | -- | 为当前单选框分组。 |
33. Switch 开关组件
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
value | bool | 必填 | -- | 设置是否选中 |
onChanged | ValueChanged<bool> | 必填 | -- | 监听选中状态的改变 |
34. 日期组件以及日期和时间戳
-
日期和时间戳
日期转化成时间戳:
print(DateTime.now().millisecondsSinceEpoch) //输出:1600925003761
时间戳转换成日期:
print(DateTime.fromMillisecondsSinceEpoch(1600925003761)) //输出:2020-09-24 13:23:23:761
-
使用系统的日期选择组件:
showDatePicker( context: context, initialDate: DateTime.now(), //默认选中的日期 firstDate: DateTime(2019), //开始(最早)日期 lastDate: DateTime(2050) //结束(最晚)日期 ).then((value) => setState(() => date = value)); //showDatePicker方法返回的是Future<DateTime>类型,所以这里使用then方法接收结果。但也可以使用async结合await的方式。
-
使用系统的时间选择组件:
showTimePicker(context: context, initialTime: TimeOfDay.now()) .then((value) => setState(() => time = value.format(context))); //showTimePicker方法返回的是Future<TimeOfDay>类型,所以这里使用then方法接收结果。但也可以使用async结合await的方式。
四、第三方组件库
虽然Flutter为我们提供了很多的Widget,但有时Flutter为我们提供的组件无法满足我们需求,这时我们就需要使用一些第三方的Widget组件。我们可以通过网站pub.dev来找到我们想要的Widget组件进行使用。
1. Toast 组件
-
使用:
-
在pubspec.yaml文件中的dependencies节点下添加toast: ^版本号:
dependencies: flutter: sdk: flutter #Toast toast: ^0.1.5 #例如这里添加0.1.5版本
-
在代码中使用:
import 'package:toast/toast.dart' Toast.show('我是一个Toast', context, gravity: Toast.CENTER);
-
2. Swiper 轮播图组件
-
使用:
-
在pubspec.yaml文件中的dependencies节点下添加flutter_swiper: ^版本号:
dependencies: flutter: sdk: flutter #轮播图组件 flutter_swiper: ^1.1.6 #例如这里添加1.1.6版本
-
在代码中使用:
import 'package:flutter_swiper/flutter_swiper.dart'; Swiper( itemCount: images.length, //设置一共有多少个item itemBuilder: (context, index) { //构建没给item return Container( child: Image.network(images[index], fit: BoxFit.cover), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), ), clipBehavior: Clip.antiAlias, ); }, pagination: SwiperPagination(),//显示指示器 autoplay: true, //是否自动播放 viewportFraction: 0.8, //当前页的占比,如果小于1,那么剩下的部分将有左右两边填充。 scale: 0.9, //每个Banner的缩放比例 onTap: (i) => setState(()=> _currentIndex = i), //条目点击监听 )
-
3. DateFormat 日期格式化组件
-
在代码中使用:
-
在pubspec.yaml文件中的dependencies节点下添加date_format: ^版本号:
dependencies: flutter: sdk: flutter #日期格式化 date_format: ^1.0.9 #例如这里添加1.0.9版本
-
使用:
由于该库使用起来稍微有点麻烦,所以我对他进行了封装(可能不是很完整,只是提供思路)。
import 'package:date_format/date_format.dart' as df; String formatDate(DateTime date, DateType type){ return df.formatDate(date, _getFormat(type)); } _getFormat(DateType type){ switch (type) { case DateType.yyyy_MM_dd: return ['yyyy', '-', 'mm', '-', 'dd']; case DateType.yyyy_MM_dd_HH: return ['yyyy', '-', 'mm', '-', 'dd', ' ', 'HH']; case DateType.yyyy_MM_dd_HH_mm: return ['yyyy', '-', 'mm', '-', 'dd', ' ', 'HH', ':', 'nn']; case DateType.yyyy_MM_dd_HH_mm_ss: return ['yyyy', '-', 'mm', '-', 'dd', ' ', 'HH', ':', 'nn', ":", 'ss']; case DateType.HH_mm_ss: return ['HH', ':', 'nn', ":", 'ss']; case DateType.HH_mm: return ['HH', ':', 'nn']; } } enum DateType{ yyyy_MM_dd, yyyy_MM_dd_HH, yyyy_MM_dd_HH_mm, yyyy_MM_dd_HH_mm_ss, HH_mm, HH_mm_ss } //使用方式如下 formatDate(date, DateType.yyyy_MM_dd)
-
五、Dialog 弹窗
如果要使用AlertDialog则需要使用showDialog(context, builder)
方法。该方法有两个核心参数如下:
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
context | BuildContext | 必填 | -- | build方法中的上下文。 |
builder | Widget Function(BuildContext context) | 必填 | -- | 构建Dialog的回调函数。 |
第一个参数context
比较简单,只要将Widget的build方法中的context或则State中的context成员传入即可。第二个参数builder
则是一个Function。你需要定义一个Function并返回你创建好的Widget,这个Widget可以是以下几种:
1. AlertDialog
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
title | Widget | 是 | -- | 设置标题 |
titlePadding | EdgeInsetsGeometry | 是 | -- | 标题的内边距 |
titleTextStyle | TextStyle | 是 | -- | 标题的字体样式 |
content | Widget | 是 | -- | 设置内容 |
contentPadding | EdgeInsetsGeometry | 是 | EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0) | 内容的内边距 |
contentTextStyle | TextStyle | 是 | -- | 内容的字体样式 |
actions | List<Widget> | 是 | -- | 设置按钮 |
showDialog(
context: context,
builder: (context){
return AlertDialog(
title: Text('提示'),
content: Text('您觉的这个Demo对您的帮助大吗?'),
actions: [
FlatButton(
child: Text('是的'),
onPressed: () {
Navigator.pop(context);
Toast.show(context, '谢谢您的认可~');
},
),
FlatButton(
child: Text('非常大'),
onPressed: () {
Navigator.pop(context);
Toast.show(context, '您真是一个好人呐~');
},
)
],
);
}
);
有些情况下我们需要获取Dialog操作后的返回值,这时可以分为以下几步:
- 自定义函数上加上
async
关键字。 - 在
showDialog
方法前面加上await
关键字并定义一个变量接收该方法的返回值。 - Navigator.pop()退出弹窗时传入要返回的值。
- 拿到showDialog方法的返回结果做相应处理。
_showAlertDialog() async{ //1.加上async关键字
var result = await showDialog( //2.加上await关键字并用变量接收
context: context,
builder: (context){
return AlertDialog(
title: Text('提示'),
content: Text('您觉的这个Demo对您的帮助大吗?'),
actions: [
FlatButton(
child: Text('是的'),
onPressed: () {
Navigator.pop(context, '您点击了按钮:是的'); //3.退出弹出时传入要返回的值。
Toast.show(context, '谢谢您的认可~');
},
),
FlatButton(
child: Text('非常大'),
onPressed: () {
Navigator.pop(context, '您点击了按钮:非常大'); //3.退出弹出时传入要返回的值。
Toast.show(context, '您真是一个好人呐~');
},
)
],
);
}
);
print(result); //4.拿到showDialog方法的返回结果做相应处理。
}
2. SimpleDialog
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
title | Widget | 是 | -- | 设置标题 |
titlePadding | EdgeInsetsGeometry | 是 | EdgeInsets.fromLTRB(24.0, 24.0, 24.0, 0.0) | 标题的内边距 |
titleTextStyle | TextStyle | 是 | -- | 标题的字体样式 |
children | List<Widget> | 是 | -- | 设置内容,通常使用SimpleDialogOption作为元素。 |
contentPadding | EdgeInsetsGeometry | 是 | EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0) | 内容的内边距 |
backgroundColor | Color | 是 | -- | 设置背景颜色 |
_showSimpleDialog() async{
var result = await showDialog(
context: context,
builder: (context){
return SimpleDialog(
title: Center(
child: Text('请您选择'),
),
children:[
SimpleDialogOption(
child: Text('第一个选项'),
onPressed: (){
Navigator.pop(context, '您点击了: 第一个选项');
},
),
Divider(height: 0),
SimpleDialogOption(
child: Text('第二个选项'),
onPressed: (){
Navigator.pop(context, '您点击了: 第二个选项');
},
),
Divider(height: 0),
SimpleDialogOption(
child: Text('第三个选项'),
onPressed: (){
Navigator.pop(context, '您点击了: 第三个选项');
},
)
],
);
}
);
print(result??'您取消了选择');
}
3. BottomSheet
如果要使用BottomSheet则需要使用showModalBottomSheet(context, builder)
方法。该方法有两个核心参数如下:
参数 | 类型 | 可选 | 默认值 | 说明 |
---|---|---|---|---|
context | BuildContext | 必填 | -- | build方法中的上下文。 |
builder | Widget Function(BuildContext context) | 必填 | -- | 构建你的自定义布局。 |
_showBottomSheet() async{
var result = await showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return Container(
height: 290,
child: Column(
children: [
ListTile(
title: Text('第一个条目'),
onTap: () => Navigator.pop(context, '第一个条目'),
),
Divider(height: 0),
ListTile(
title: Text('第二个条目'),
onTap: () => Navigator.pop(context, '第二个条目'),
),
Divider(height: 0),
ListTile(
title: Text('第三个条目'),
onTap: () => Navigator.pop(context, '第三个条目'),
),
Divider(height: 0),
ListTile(
title: Text('第四个条目'),
onTap: () => Navigator.pop(context, '第四个条目'),
),
Divider(height: 0),
ListTile(
title: Text('第五个条目'),
onTap: () => Navigator.pop(context, '第五个条目'),
),
],
),
);
}
);
print('您点击了:$result');
}
4. 自定义Dialog
在Flutter中,如果要自定义Dialog,需要以下几个步骤:
- 声明一个类并继承自Dialog。
- 重写Dialog中的
Widget build(BuildContext context)
方法。 - 在
Widget build(BuildContext context)
方法中返回的Widget必须以Material组件作为根节点。 - 通常情况下我们的Dialog都是有透明背景的,就是Dialog弹出后仍然能看到一部分原来的页面,所以我们需要给Material组件设置type属性为:
MaterialType.transparency
。
下面的栗子就是自定义一个Dialog
class CustomDialog extends Dialog {
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: Stack( //从这里开始就可以写我们自己想要的界面了。
alignment: Alignment.center,
children: [
Container(
margin: EdgeInsets.only(left: 40, right: 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8))),
child: Column(
children: [
SizedBox(height: 12),
Text('提示',
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 15)),
SizedBox(height: 12),
Divider(height: 0),
],
),
),
Container(
padding: EdgeInsets.all(12),
color: Colors.white,
constraints: BoxConstraints(minHeight: 100),
child: Center(
child: Text('这是一个自定义弹出,你知道了吗?'),
),
),
Divider(height: 0),
Row(
children: [
Expanded(
child: Container(
height: 50,
child: FlatButton(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(8))),
child: Text('不知道'),
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
onPressed: () {
Navigator.pop(context, '我看你该挨揍了。');
},
),
),
),
VerticalDivider(width: 0.1),
Expanded(
child: Container(
height: 50,
child: FlatButton(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(8))),
child: Text('知道了',
style: TextStyle(color: Colors.blue)),
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
onPressed: () {
Navigator.pop(context, '不错,你很聪明。');
},
),
),
)
],
)
],
),
)
],
),
);
}
}
使用上面的自定义Dialog和普通的Dialog没有什么区别,下面是使用时的代码:
_showCustomDialog() async {
var result = await showDialog(
context: context,
builder: (context) {
return CustomDialog();
});
print(result);
}
如果要实现打开弹窗后的一段时间之后自动关闭弹窗则可以结合Dart中的Timer定时器实现:
Timer.periodic(Duration(seconds: 5), (timer) {
timer.cancel();
//do something,such as dismiss the dialog.
});
六、注解
1. @override 重写
该注解用于表示一个成员是继承自父类。
2. @protected
该注解只能应用于一个类中的成员,表示该成员只能被子类调用或扩展,也可以在混合类中直接或间接的调用。
3. @mustCallSuper
该注解应用在方法上,表示该方法如果被子类重写了,那么子类则必须调用super。
七、路由
Flutter中路由的核心类为Navigator,如果要打开新的页面则需要调用push
方法,如果要退出页面则需要调用pop
方法。
1. 普通路由
Navigator.push(context, MaterialPageRoute(
builder:(context){
return SecondPage(); //这里返回目标页面。
}
));
2. 普通路由传值
普通路由的传值方式其实就是在构造要跳转的页面时将参数传入。
Navigator.push(context, MaterialPageRoute(
builder:(context){
return SecondPage(title: '第二个页面'); //这里构建要跳转的页面时直接传入参数。
}
));
class SecondPage extends StatelessWidget{
final String title;
SecondPage({this.title = ""}); //这里接受外面传入的参数
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title), //这里使用外面传入的参数
),
body: Align(
alignment: Alignment.center,
child: Text("我是${this.title}"), //这里使用外面传入的参数
),
);
}
}
3. 命名路由
命名路由的使用分以下两步:
-
在主入口中的MaterialApp中配置
routes
,routes
接受的数据类型时Map,Map的key是路由命名,值是初始具体跳转到某个页面的回调。void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, ), home: Tabs(), routes: { //使用routes参数配置命名路由。 '/second': (context) => SecondPage() //这里定义了一个命名路由‘/second’并制定跳转到SecondPage页面。 }, ); } }
-
利用
pushNamed
方法使用已经定义好的路由。Navigator.pushNamed(context, '/second'); //这里的‘/second’就是我们已经定义好的路由,必须和已经定义好的名称保持一致。
4. 命名路由传值
命名路由的传值略微有点复杂,具体代码如下:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
//将我们所有的路由定义为成员变量。
final routes = {
'/second': (context, {arguments}) => SecondPage(title: arguments['title'])
};
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Tabs(),
onGenerateRoute: (RouteSettings settings){ //利用onGenerateRoute自己处理传参及跳转。
final String name = settings.name; //获取路由名称
final Function pageContentBuilder = this.routes[name]; //根据名称获取具体跳转的方法
if(pageContentBuilder != null) { //判断方法是否为空,如果空则抛出异常。
return MaterialPageRoute(
builder: (context) {
if(settings.arguments != null) { //如果有参数则调用我们自定义的方法传入参数
return pageContentBuilder(context, arguments: settings.arguments);
}else {
return pageContentBuilder(context); //没有参数时则忽略arguments
}
}
);
}else {
throw ArgumentError('route "$name" not implements!');
}
},
);
}
}
路由已经定义好了,那么调用的方式如下:
Navigator.pushNamed(context, '/second', arguments: {
'title': '第二个页面'
});
代码抽离:
-
入口文件
import 'package:flutter/material.dart'; import 'package:flutter_demo_one/pages/route.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, ), initialRoute: '/', onGenerateRoute: generateRoute, ); } }
-
Route文件
import 'package:flutter/material.dart'; import 'package:flutter_demo_one/pages/hospital/hospitalDetailPage.dart'; import 'pages/main/tabs.dart'; final routes = { '/': (context) => Tabs(), '/hospital': (context, {arguments}) => HospitalDetailPage(arguments) }; Function generateRoute = (RouteSettings settings){ final String name = settings.name; final Function pageContentBuilder = routes[name]; if(pageContentBuilder != null) { return MaterialPageRoute( builder: (context) { if(settings.arguments != null) { return pageContentBuilder(context, arguments: settings.arguments); }else { return pageContentBuilder(context); } } ); }else { throw ArgumentError('route "$name" not implements!'); } };
5. 替换路由
替换路由其实就是用要打开的新的页面来替换当前页面,例如:A是第一页面(主页面),A正常打开了B页面,而B又用替换路由打开了C,那么此时的页面栈中就只有AC两个页面,如果在C页面中点击返回按钮后( Navigator.pop()
)就会直接显示A而不会显示B。
注意:使用替换路由时,被打开的页面是否有返回按钮(左上角的返回箭头)取决于当前页面是否有返回按钮。
Navigator.pushReplacementNamed(context, '/testPage2', arguments: {
'title':'页面标题'
});
八、 网络请求
1. Json 与 Map 相互转换。
-
Json转Map
final String _json = '{"username":"张三","age":23}'; //json字符串数据 print(json.decode(_json)['username']) //输出:张三
-
Map转Json
final Map _map = { "username":"张三", "age": 23 }; print(context, json.encode(_map)) //输出:{"username":"张三","age":23}
2. 使用http库进行网络请求
-
使用:
-
在pubspec.yaml文件中的dependencies节点下添加http: ^版本号:
dependencies: http: ^0.12.2
-
在代码中使用:
get请求
import 'package:http/http.dart'; _onGetData(apiUrl) async{ var result = await get(apiUrl); if(result.statusCode == 200) { print(result.body); }else { print('接口请求错误!'); } }
post请求
_doLogin(context, username, psw, {url = ''}) async { var result = await post(url, body: {'username':username, 'psw':psw}); if (result.statusCode == 200) { print(result.body); } else { print('接口请求错误!'); } }
-
3. dio 库请求
dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等...
地址:https://pub.dev/packages/dio github地址:https://github.com/flutterchina/dio
-
使用:
-
在pubspec.yaml文件中的dependencies节点下添加dio: ^版本号:
dependencies: dio: ^3.0.7
-
简单使用
import 'package:dio/dio.dart'; void getHttp() async { try { Response response = await Dio().get("http://www.baidu.com"); print(response); } catch (e) { print(e); } }
-
发起一个
GET
请求 :Response response; Dio dio = Dio(); response = await dio.get("/test?id=12&name=wendu") print(response.data.toString()); // 请求参数也可以通过对象传递,上面的代码等同于: response = await dio.get("/test", queryParameters: {"id": 12, "name": "wendu"}); print(response.data.toString());
-
发起一个
POST
请求:response = await dio.post("/test", data: {"id": 12, "name": "wendu"});
-
发起多个并发请求:
response = await Future.wait([dio.post("/info"), dio.get("/token")]);
-
下载文件:
response = await dio.download("https://www.google.com/", "./xx.html");
-
以流的方式接收响应数据:
Response<ResponseBody> rs = await Dio().get<ResponseBody>(url, options: Options(responseType: ResponseType.stream), //设置接收类型为stream ); print(rs.data.stream); //响应流
-
以二进制数组的方式接收响应数据:
Response<List<int>> rs = await Dio().get<List<int>>(url, options: Options(responseType: ResponseType.bytes), //设置接收类型为bytes ); print(rs.data); //二进制数组
-
发送 FormData:
FormData formData = FormData.from({ "name": "wendux", "age": 25, }); response = await dio.post("/info", data: formData);
-
通过FormData上传多个文件:
FormData.fromMap({ "name": "wendux", "age": 25, "file": await MultipartFile.fromFile("./text.txt",filename: "upload.txt"), "files": [ await MultipartFile.fromFile("./text1.txt", filename: "text1.txt"), await MultipartFile.fromFile("./text2.txt", filename: "text2.txt"), ] }); response = await dio.post("/info", data: formData);
-
监听发送(上传)数据进度:
response = await dio.post( "http://www.dtworkroom.com/doris/1/2.0.0/test", data: {"aa": "bb" * 22}, onSendProgress: (int sent, int total) { print("$sent $total"); }, );
-
以流的形式提交二进制数据:
// 二进制数据 List<int> postData = <int>[...]; await dio.post( url, data: Stream.fromIterable(postData.map((e) => [e])), //创建一个Stream<List<int>> options: Options( headers: { Headers.contentLengthHeader: postData.length, // 设置content-length }, ), );
注意:如果要监听提交进度,则必须设置content-length,反之则是可选的。
-
九、其他
1. 国际化
-
第一步:找到pubspec.yaml文件,配置flutter_localizations(国际化)。
dependencies: flutter: sdk: flutter #国际化配置 flutter_localizations: sdk: flutter
-
第二步:导入国际化的包flutter_localizations(AS可以自动导包)。
import 'package:flutter_localizations/flutter_localizations.dart';
-
第三步:在main入口中的MaterialApp组件中配置国际化:
import 'package:flutter/material.dart'; import 'package:flutter_demo_one/route.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue ), initialRoute: '/', onGenerateRoute: generateRoute, //以下是国际化配置,配置下面localizationsDelegates和supportedLocales两个属性即可实现国际化。 localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate ], supportedLocales: [ const Locale('zh', 'CH'), const Locale('en', 'US') ], ); } }
2. 沉浸式状态栏
只需要在入口处build方法中return前加入下面一行代码,将状态栏的颜色设置为透明色即可:
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: Colors.transparent));