原文地址:https://siques.cn/p/34
这个课程我们继续介绍一些重要的 Material 风格的小部件 … Chip 是小碎片,我们会学习创建各种不同类型的 Chip … 比如带删除功能的 Chip … 像按钮的 Chip … 像复选框一样的 Chip … 还有像单选按钮一样的 Chip …
然后再了解一下使用 DataTable 小部件去创建一个数据表 … 了解一下数据表的排序功能 … 还有数据行的选择功能 … 我们还会去创建一个可以分页显示数据的数据表 …
接着我们会再了解一下卡片小部件,添加一组卡片 … 还有一个步骤小部件 … 可以用它展示用户要完成的一系列的步骤 …
Chip
Chip … 就是一种像小标签,小碎片的东西。比如给内容选择的内容标签,过滤的条件这些东西,都可以使用 Chip 小部件来展示它们。
Chip:小碎片
几种chip的展示
代码块:
Chip(
label: Text("Life"),
),
SizedBox(width: 8.0),
Chip(
label: Text("Sunset"),
backgroundColor: Colors.orange,
),
SizedBox(width: 8.0),
Chip(
label: Text("Shenghao"),
avatar: CircleAvatar(
backgroundColor: Colors.grey,
child: Text('豪'),
),
),
SizedBox(width: 8.0),
Chip(
label: Text("Shenghao"),
avatar: CircleAvatar(
backgroundImage: NetworkImage(<图片地址>),
),
)
Wrap:换行显示小部件
现在界面的右边会出现一个警告,提示我们有一部分内容超出了屏幕的显示 … 这里我们可以用一个 Wrap 小部件去包装一下这些 Chip … 这样如果内容超出了屏幕的显示,会另起一行显示它
结构:
代码块:
import "package:flutter/material.dart";
class ChipDemo extends StatefulWidget {
ChipDemo({Key key}) : super(key: key);
@override
_ChipDemoState createState() => _ChipDemoState();
}
~开始~
class _ChipDemoState extends State<ChipDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ChipDemo'),
elevation: 0,
),
body: Container(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Wrap(
spacing: 8.0,
// 行间隔
runSpacing: 8.0,
children: <Widget>[
Chip(
label: Text("Life"),
),
Chip(
label: Text("Sunset"),
backgroundColor: Colors.orange,
),
Chip(
label: Text("Shenghao"),
avatar: CircleAvatar(
backgroundColor: Colors.grey,
child: Text('豪'),
),
),
Chip(
label: Text("Shenghao"),
avatar: CircleAvatar(
backgroundImage: NetworkImage(""),
),
)
],
),
],
),
),
);
}
}
~结束~
Divider:分隔符
Divider 这个小部件可以在界面上添加一个分隔符 … 在这组 Chip 小部件的下面,添加一个 Divider … 保存一下 … 你会发现界面上会显示一条浅灰色的分隔线 …
Chip:带删除功能的小碎片
在这个分隔符的上面,再添加一个 Chip …
设置一下它的 label … 一个 Text 小部件 … 文字是 City … 在 Chip 上面可以添加一个删除小图标 …
按下这个删除小图标会执行 onDeleted ,先添
加一个 onDeleted … 给它一个空白的方法 … 现在这个 Chip 的右边会出现一个默认的删除按钮 … 按一它就会执行 onDeleted 指定的动作 …
Chip:用列表生成带删除功能的小碎片
先在类里面添加一个列表数据 … 列表项目的值是 String … 列表的名字叫 _tags … 里面添加几个字符串类型的列表数据 … 苹果 … 香蕉 … 还有柠檬 …
下面我们可以基于这个列表数据去生成一组 Chip … 可以把它们放在一个 Wrap 里面 … 添加一个 spacing 设置一下项目之间的间隔 … 它的 children 属性可以用一下_tags … map ,迭代处理一下 _tags 里的项目 … 处理的结果转换成一个 List … 用一下 toList .
代码块:
import "package:flutter/material.dart";
class ChipDemo extends StatefulWidget {
ChipDemo({Key key}) : super(key: key);
@override
_ChipDemoState createState() => _ChipDemoState();
}
~开始~
class _ChipDemoState extends State<ChipDemo> {
List<String> _tags = ['Apple', 'Banana', "Lemon"];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ChipDemo'),
elevation: 0,
),
body: Container(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Wrap(
spacing: 8.0,
// 行间隔
runSpacing: 8.0,
children: <Widget>[
Divider(
color: Colors.grey,
height: 32.0,
indent: 32.0,
),
Wrap(
spacing: 8.0,
children: _tags.map((tag) {
return Chip(
label: Text(tag),
onDeleted: () {
setState(() {
_tags.remove(tag);
});
},
);
}).toList(),
)
],
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.restore),
onPressed: () {
setState(() {
_tags = ['Apple', 'Banana', "Lemon"];
});
},
),
);
}
}
~结束~
ActionChip:动作碎片
ActionChip 可以带一个动作,所以它的功能有点像是按钮 … 先复制一份上面这个 Divider 还有这个 Wrap 小部件 …
生成的这组 Chip 可以换成 ActionChip … 这样它里面就不能再用 onDelete 了 … ActionChip 里提供了一个 onPressed … 点按 Chip 会执行这个属性指定的动作 … 先给它一个空白的方法 …
FilterChip:过滤碎片
FilterChip 的功能跟 Checkbox 或者 Switch 有点像 … 它会有选中还有未选中两种状态 … 先复制一份之前添加的 Divider … Container … 还有这个 Wrap …
修改一下标题文字 … FilterChip … 后面加上一个 _selected.toString() … 这个 _selected 是一个列表,表示的就是被选中的项目,toString 这个方法可以把列表转换成字符串 …
ChoiceChip:选择碎片
ChoiceChip 有点像是单选按钮 … 表示在一组东西里面的一个唯一的选择 … 先在这个类里面添加一个新的数据 … 类型是 String … 名字可以叫 _choice … 先让它等于 Lemon …
DataTable
DataTable 小部件可以创建数据表格 … 我们可以把它放在一个 ListView 视图里面 … 把这个 Column 换成一个 ListView … 再去掉 mainAxisAlignment 属性 …
DataTable:数据表格
在 ListView 的 children 里面,添加一个 DataTable … 这个小部件有两个必须的属性,columns 还有 rows … columns 里面是数据表格的栏目 …
每个栏目可以用一个 DataColumn 小部件创建 … 这个小部件里面需要一个 label 属性 … 一个 Text ,显示的文字是 Title …
复制一份,再添加一个栏目 … 栏目的标签是 Author …
现在模拟器上显示的就是一个 DataTable … 数据表格 … 这个表格里面有两个 DataColumn … Title 还有 Author …
表格里面有两行具体的内容 … 每一行内容都是一个 DataRow … 这行内容里面,每个单元格用的是 DataCell …
DataTable:用列表生成数据表格
下面我们可以一个列表去生成数据表格里的内容 … 在项目的 model 目录的下面,有个 post.dart … 这个文件里面定义了一个列表数据叫 posts … 每个项目都是一个 Post … 里面有 title … author … description 还有 imageUrl 这几个属性 …
再回到我们的 DataTable 的演示 … 先在这个文件的顶部导入刚才的文件 … 位置是上一级目录下面的 model 里面的 post.dart …
找到之前添加的 DataTable 里的 rows 属性 … 去掉手工给它设置的这个 DataRow 列表 … 这个 DataRow 列表现在我们可以使用一个列表数据生成 …
用一下 posts … map ,把迭代的结果转换成一个列表 … 用一下 toList 方法 …
DataTable:数据表格的排序
在 DataTable 小部件上添加一个 sortColumnIndex … 它的值应该是排序的栏的索引号 … 比如表示按第一栏内容排序,它的值应该设置成 0 … 你会发现这个栏目的标签旁边会出现一个表示排序的符号 …
默认是升序排列 … 表示表格当前用的排序方法,可以使用 sortAscending 属性控制 … 如果是 true,就表示数据表格正在按栏目升序排列 … 如果把它的值设置成 false,就表示当前表格是降序排列 …
这两个属性的值应该动态去设置 … 用 _sortColumnIndex 的值表示 sortColumnIndex … 下面用 _sortAscending 的值表示 sortAscending …
代码块:
import "package:flutter/material.dart";
import 'package:shenghao_flutter/model/post.dart';
class DataTableDemo extends StatefulWidget {
DataTableDemo({Key key}) : super(key: key);
@override
_DataTableDemoState createState() => _DataTableDemoState();
}
~开始~
class _DataTableDemoState extends State<DataTableDemo> {
int _sortColumnIndex;
bool _sortAscending = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('DataTableDemo'),
elevation: 0,
),
body: Container(
padding: EdgeInsets.all(16.0),
child: ListView(children: <Widget>[
DataTable(
sortColumnIndex: _sortColumnIndex,
sortAscending: _sortAscending,
columns: [
DataColumn(
label: Text('Title'),
onSort: (int index, bool ascend) {
setState(() {
_sortColumnIndex = index;
_sortAscending = ascend;
posts.sort((a, b) {
if (!ascend) {
final c = a;
a = b;
b = c;
}
return a.title.length.compareTo(b.title.length);
});
});
},
),
DataColumn(label: Text('Author')),
// DataColumn(label: Text('Image'))
],
rows: posts.map((post) {
return DataRow(cells: [
DataCell(Text(post.title)),
DataCell(Text(post.author)),
// DataCell(Image.network(post.imageUrl)),
]);
}).toList())
]),
),
);
}
}
~结束~
DataTable:选择数据表格行
在数据表格的 DataRow 里面,selected 属性的值表示的是行的选择状态 … 这个状态我们可以使用 post 里的 selected 属性来表示 … 然后找到 model 下面的这个 post.dart … 按住 alt 键,点一下 posts,可以打开定义这个 posts 的地方 .
在 Post 里面,添加一个 bool 类型的 selected … 默认让它的值等于 false …
按下行 … 可以切换行的选择状态 …
表格栏目名字的左边有一个复选框可以切换所有行的选择状态 … 如果你想定制这个全选的动作,可以在 DataTable 里面添加一个 onSelectAll 方法 … 它也接收一个 bool 类型的 value 参数 … 这个 value 表示的就是全选的状态 …
PaginatedDataTable:分页显示表格数据
先找到这个 DataTable … 把它换成一个 PaginatedDataTable … 这样之前我们在 DataTable 里面添加的 rows 这个属性就不再需要了 … 表格里的具体的数据要使用一个 source 来设置 … 先复制一下这个 cells 里面添加的几个单元格 … 一会儿会用到 …
代码块:
import "package:flutter/material.dart";
import 'package:shenghao_flutter/model/post.dart';
import "../model/post.dart";
class PostsDataSource extends DataTableSource {
final List<Post> _posts = posts;
int _selectedCount = 0;
@override
int get rowCount => _posts.length;
@override
// TODO: implement isRowCountApproximate
bool get isRowCountApproximate => false;
@override
// TODO: implement selectedRowCount
int get selectedRowCount => _selectedCount;
@override
DataRow getRow(int index) {
final Post post = _posts[index];
return DataRow.byIndex(index: index, cells: <DataCell>[
DataCell(Text(post.title)),
DataCell(Text(post.author)),
]);
}
}
~开始~
class PaginatedDataTableDemo extends StatefulWidget {
PaginatedDataTableDemo({Key key}) : super(key: key);
@override
_DataTableDemoState createState() => _DataTableDemoState();
}
class _DataTableDemoState extends State<PaginatedDataTableDemo> {
int _sortColumnIndex;
bool _sortAscending = true;
final PostsDataSource _postsDataSource = PostsDataSource();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PaginatedDataTableDemo'),
elevation: 0,
),
body: Container(
padding: EdgeInsets.all(16.0),
child: ListView(children: <Widget>[
PaginatedDataTable(
header: Text('Posts'),
rowsPerPage: 2,
source: _postsDataSource,
sortColumnIndex: _sortColumnIndex,
sortAscending: _sortAscending,
// onSelectAll: (bool value) {},
columns: [
DataColumn(
label: Text('Title'),
onSort: (int index, bool ascend) {
setState(() {
_sortColumnIndex = index;
_sortAscending = ascend;
posts.sort((a, b) {
if (!ascend) {
final c = a;
a = b;
b = c;
}
return a.title.length.compareTo(b.title.length);
});
});
},
),
DataColumn(label: Text('Author')),
// DataColumn(label: Text('Image'))
],
)
]),
),
);
}
}
~结束~
PaginatedDataTable:排序
表格里面还有个排序的功能,我们需要再修改一下 … 先找到这个可以排序的栏目 … 它上面添加了 onSort ,表示这个栏目是可以排序的 …
这个方法接收两个参数,一个是 int 类型的 index,表示栏目的索引号,这个名字可以换成 columnIndex ,这样更容易明白它表示的东西 …
下面再到 PostDataSoruce
里面,去添加一个方法 … 名字是 _sort … 第一个参数是个方法参数,名字可以叫它 getField … 这个方法参数接受一个 post 参数 … 这样在这个 _sort 里面,使用这个 getField 参数的时候,我们需要给它提供一个 post ,就是内容列表里面的一个项目 …
第二个参数是 bool 类型的 ascending … 里面用一下 _posts 上的 sort 方法,一个方法 ,两个参数,a 还有 b ,表示的就是列表里面有两个项目 … 先判断一下,如果 !ascending … 我们可以让 a 还有 b 的值调换一下 … 先让 c 等于 a … a 等于 b ,再让 b 等于 c …
下面再添加一个 aValue … 它的值用一下 getField ,把 a 交给它 … 对于数据表里面的 Title 这栏的排序来说,这个 aValue 应该就是 a 这个文章内容的 title 的长度,也就是它的标题字符长度 … 因为在使用 _sort 这个方法的时候,给它的 getField 方法参数设置的就是让它返回 post 里的 title 的 length 属性的值 …
下面再添加一个 bValue ,用一下 getField,把 b 交给它 .
方法 return 的是,用一下 Comparable 上面的 compare 方法,比较一下 aValue … 还有 bValue …
排序完成以后,需要再执行一下 notifyListeners() …
import "package:flutter/material.dart";
import 'package:shenghao_flutter/model/post.dart';
import "../model/post.dart";
class PostsDataSource extends DataTableSource {
final List<Post> _posts = posts;
int _selectedCount = 0;
@override
int get rowCount => _posts.length;
@override
// TODO: implement isRowCountApproximate
bool get isRowCountApproximate => false;
@override
// TODO: implement selectedRowCount
int get selectedRowCount => _selectedCount;
@override
DataRow getRow(int index) {
final Post post = _posts[index];
return DataRow.byIndex(index: index, cells: <DataCell>[
DataCell(Text(post.title)),
DataCell(Text(post.author)),
]);
}
void _sort(getField(post), bool ascending) {
_posts.sort((a, b) {
if (!ascending) {
final c = a;
a = b;
b = c;
}
final aValue = getField(a);
final bValue = getField(b);
return Comparable.compare(aValue, bValue);
});
notifyListeners();
}
}
~开始~
class PaginatedDataTableDemo extends StatefulWidget {
PaginatedDataTableDemo({Key key}) : super(key: key);
@override
_DataTableDemoState createState() => _DataTableDemoState();
}
class _DataTableDemoState extends State<PaginatedDataTableDemo> {
int _sortColumnIndex;
bool _sortAscending = true;
final PostsDataSource _postsDataSource = PostsDataSource();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PaginatedDataTableDemo'),
elevation: 0,
),
body: Container(
padding: EdgeInsets.all(16.0),
child: ListView(children: <Widget>[
PaginatedDataTable(
header: Text('Posts'),
rowsPerPage: 2,
source: _postsDataSource,
sortColumnIndex: _sortColumnIndex,
sortAscending: _sortAscending,
// onSelectAll: (bool value) {},
columns: [
DataColumn(
label: Text('Title'),
onSort: (int columnindex, bool ascending) {
_postsDataSource._sort(
(post) => post.title.length, ascending);
setState(() {
_sortColumnIndex = columnindex;
_sortAscending = ascending;
});
},
),
DataColumn(label: Text('Author')),
// DataColumn(label: Text('Image'))
],
)
]),
),
);
}
}
~结束~
Card
下面我们试一下 Flutter 的 Card 小部件 … 我们可以把一组卡片小部件放在一个 ListView 里面 … 这个 ListView 的孩子们可以使用一个 map 方法来生成 … 用一下 posts.map … 结果执行一下 toList 转换成一个列表 …
卡片
也许这是最丑的卡片:
ClipRRect:圆角(给图像添加圆角效果)
卡片小部件默认有一个大小是 4.0 的圆角效果 … 不过这里卡片的上面这个圆角被图片挡住了 … 我们可以给图像的左上角还有右上角添加一个大小跟卡片小部件一样的圆角效果 …
可以这样 … 先找到卡片上的图像 … 剪切一下 … 然后用一个 ClipRRect … 它会给它的孩子裁出来一个圆角 … 添加一个 borderRadius 属性 … 对应的值用一下 BorderRadius.only … 先设置一下 topLeft 这个角的圆角 … Radius.circular … 大小是 4.0 … 复制一份 … 再设置一下 topRight 这个角的圆角 … 大小也是 4.0 …
Stepper
Stepper:步骤
Stepper,步骤小部件 … 可以用它展示用户需要完成一系列的步骤 … 在这个 Stepper 演示里面 … 我们可以先用一个 Theme … 覆盖掉应用的部分主题的配置 … 一个 data 属性 … 它的值用一下 Theme.of context … copyWith … 里面重新设置一下主题的 primaryColor … 颜色可以设置成黑色 …
安装一个生成随机内容的插件