前言

本篇文的目的是熟练掌握 Flutter 组件的封装,并且使用回调函数实现主要功能。

本组件的设计灵感来源于 Element 组件库的 table 组件。

正题

定义回调函数

在此之前,必须要了解在 Dart 中如何定义回调函数。

回调函数必须使用typedef关键字声明:

typedef OnCreated = void Function(String e);

注意:函数的别名声明的位置最好在类之外。

在 Person 类中的 create 函数里创建回调函数:

class Person {
void create(String e, OnCreated callback) {
callback(e);
}
}

main函数使用 create 方法:

void main() {
Person().create('hello world!', (e) { print(e); });
}

创建列表组件

通过本篇文章实现如图所示的列表组件:

创建 StatefulWidget

将组件命名为 ActionableList,由于列表的项中间部分的内容可能随着用户修改而修改,所以定义为 StatefulWidget。

class ActionableList extends StatefulWidget {
const ActionableList({Key? key}) : super(key: key); @override
State<ActionableList> createState() => _ActionableListState();
} class _ActionableListState extends State<ActionableList> {
@override
Widget build(BuildContext context) {
return Column();
}
}

ActionableListTemplate

列表组件下有许多项,每一项的布局是左中右,左边为 Text 组件,中间为 Widget 类型的组件,右边为 Icon 组件,整个列表的项是可以被点击的。

ActionableListTemplate 的用作是约束以什么结构来渲染列表的每一项。

class ActionableListTemplate {
final String label;
final String middle;
final IconData icon; ActionableListTemplate({
required this.label,
required this.middle,
this.icon = Icons.arrow_forward_ios
});
}

使用 ActionableList 组件时必须传递数组,其中项为 ActionableListTemplate:

class ActionableList extends StatefulWidget {
final List<ActionableListTemplate> template; const ActionableList({Key? key, required this.template}) : super(key: key);
}

构建 ActionableList 的界面

实现 ActionableList 的 UI,在 build 函数中,为了时代码更有阅读性,每一个步骤抽取到一个函数中:

第一步,创建列表的一个项:

Widget _createItem(String label, Widget middle, IconData icon) {
return InkWell(
child: Padding(
child: Row(
children: [
Text(
label,
style: TextStyle(color: widget.labelColor),
),
Expanded(child: middle),
Icon(icon),
],
),
),
);
}

第二步,创建列表:

List<Widget> _createList() {
List<Widget> list = [];
for (int i = 0; i < widget.template.length; i++) {
list.add(
_createItem(widget.template[i].label, widget.template[i].middle, widget.template[i].icon));
}
return list;
}

build 函数只需要调用 _createList 函数创建一个列表即可:

@override
Widget build(BuildContext context) {
return Column(
children: _createList(),
);
}

使用 ActionableList

class _UserCenterSliceState extends State<UserCenterSlice> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ActionableList(
template: [
ActionableListTemplate(
label: '头像',
middle: Avatar(url: 'assets/images/icon')
),
ActionableListTemplate(
label: '昵称',
content: Text('shiramashiro')
),
ActionableListTemplate(
label: '性别',
content: Text('男'),
),
],
);
}
}

缺陷分析

虽然 ActionableListTemplate 的 content 属性可以插入各式各样的 Widget,但是这些 Widget 内的字符串、数值等数据无法根据业务需求而灵活地变更。一般,这些数据都是来源于请求得来的 JSON 格式数据。

请看下面给出的简单例子:

假如有一个 JSON 数据,其中一个字段为 hobbies,有的用户有三个、有的用户有四个等等情况,数据不是死的,而是灵活的。

final jsonData = {
hobbies: [ '打篮球', '看小说', '编程' ]
} .... ActionableListTemplate(
label: '兴趣',
content: Row(children: [ Text('打篮球'), Text('看小说'), Text('编程') ])
),

也可以在使用组件的时候,专门写一个函数对该字段进行循环。也是可以的,但是不优雅,不“好看”。

改进思路

更好的方式就是,把请求过来的数据直接交给 ActionableList 管理。首先,ActionableList 肯定是要通过 ActionableListTemplate 构建列表;其次,在 content 字段这里,可以更加灵活一点,比如 A 页面使用了列表组件,利用回调函数把对应的字段返回到 A 页面这一层面中,在 A 页面里写逻辑、写函数、写 Widget 等等。

改造

添加新的属性

在类中为其构造函数添加一个参数:

class ActionableList extends StatefulWidget {
... final Map<dynamic, dynamic> data; const ActionableList({Key? key, required this.data, ...}) : super(key: key); @override
State<ActionableList> createState() => _ActionableListState();
}

添加回调函数

在类外部定义一个回调函数,返回类型为 Widget,并且接收一个 dynamic 类型的参数,这个参数可以被外部获得:

typedef Created = Widget Function(dynamic e);

修改属性

ActionableListTemplate 添加属性,并将原本的 content 属性改名为 String 类型的 filed 属性:

class ActionableListTemplate {
...
final String field; // 原本是 Widget 类型,现在是 String 类型。
...
final Created created; // created 将作为回调函数返回 filed 对应的 JSON 数据。 ActionableListTemplate({
...
required this.field,
...
required this.created,
});
}

修改 _createItem

在 _createItem 函数中添加一个参数:

Widget _createItems(
...
String filed,
...
Created created, // 新增参数
) {
Widget middle = created(filed); // 把 filed 属性传给 created 回调函数,在外部可以通过回调函数取到该值。
...
return (
...
Expanded(child: middle),
);
}

created 回调函数在往外传递数据时,也将得到一个 Widget 类型的变量,然后将其插入到 Expanded(child: middle) 中。

效果示范

第一步,提供一个 Map 类型的数据:

Map<String, dynamic> data = {
'uname': '椎名白白',
'sex': '男',
'avatar': 'assets/images/95893409_p0.jpg'
}; @override
Widget build(BuildContext context) {
return Scaffold(
body: ActionableList(
data: data,
template: [
ActionableListTemplate(
label: '头像',
field: 'avatar',
created: (e) => Avatar(url: e, size: 50), // e 就是 data['avatar']
),
ActionableListTemplate(
label: '昵称',
field: 'uname',
created: (e) => Text(e), // e 就是 data['uname']
),
ActionableListTemplate(
label: '性别',
field: 'sex',
created: (e) => Text(e),
),
],
),
);
}

效果就是,提供一个 JSON 格式数据给 ActionableList,然后为 ActionableListTemplate 指定一个 filed 属性,其对应这 JSON 的每一个字段。最后,如何构造列表项中间的 Widget,由 A 页面这里提供,也就是在 created 回调函数里构建,并且能够把对应的值给插入到任何位置。

完整示例

actionable_list.dart:

import 'package:flutter/material.dart';

typedef OnTap = void Function();
typedef Created = Widget Function(dynamic e); class ActionableListTemplate {
final String label;
final String field;
final IconData icon;
final OnTap onTap;
final Created created; ActionableListTemplate({
required this.label,
required this.field,
this.icon = Icons.arrow_forward_ios,
required this.onTap,
required this.created,
});
} class ActionableList extends StatefulWidget {
final Map<dynamic, dynamic> data;
final List<ActionableListTemplate> template;
final double top;
final double left;
final double right;
final double bottom;
final Color labelColor; const ActionableList({
Key? key,
required this.data,
required this.template,
this.top = 10,
this.right = 10,
this.left = 10,
this.bottom = 10,
this.labelColor = Colors.black,
}) : super(key: key); @override
State<ActionableList> createState() => _ActionableListState();
} class _ActionableListState extends State<ActionableList> {
Widget _createItems(
String label,
String filed,
IconData icon,
OnTap onTap,
Created created,
) {
Widget middle = created(filed);
return InkWell(
onTap: onTap,
child: Padding(
padding: EdgeInsets.only(
left: widget.left,
top: widget.top,
right: widget.right,
bottom: widget.bottom,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
label,
style: TextStyle(
color: widget.labelColor,
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [middle],
),
),
),
Icon(icon),
],
),
),
);
} List<Widget> _createList() {
List<Widget> list = [];
for (int i = 0; i < widget.data.length; i++) {
list.add(
_createItems(
widget.template[i].label,
widget.data[widget.template[i].field],
widget.template[i].icon,
widget.template[i].onTap,
widget.template[i].created,
),
);
}
return list;
} @override
Widget build(BuildContext context) {
return Column(
children: _createList(),
);
}
}

user_center_slice.dart:

import 'package:flutter/material.dart';
import 'package:qingyuo_mobile/components/actionable_list.dart';
import 'package:qingyuo_mobile/components/avatar.dart'; class UserCenterSlice extends StatefulWidget {
const UserCenterSlice({Key? key}) : super(key: key); @override
State<UserCenterSlice> createState() => _UserCenterSliceState();
} class _UserCenterSliceState extends State<UserCenterSlice> {
Map<String, dynamic> data = {
'uname': '椎名白白',
'sex': '男',
'signature': 'Time tick away, dream faded away!',
'uid': '7021686',
'avatar': 'assets/images/95893409_p0.jpg'
}; @override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: const Color.fromRGBO(147, 181, 207, 6),
title: const Text("账号资料"),
),
body: ActionableList(
data: data,
template: [
ActionableListTemplate(
label: '头像',
field: 'avatar',
onTap: () {},
created: (e) => Avatar(url: e, size: 50),
),
ActionableListTemplate(
label: '昵称',
field: 'uname',
onTap: () {},
created: (e) => Text(e),
),
ActionableListTemplate(
label: '性别',
field: 'sex',
onTap: () {},
created: (e) => Text(e),
),
ActionableListTemplate(
label: '个性签名',
field: 'signature',
onTap: () {},
created: (e) => Text(e),
),
ActionableListTemplate(
label: 'UID',
field: 'uid',
onTap: () {},
created: (e) => Text(e),
)
],
),
);
}
}

Flutter 实战(一):列表项内容可自定义的列表组件的更多相关文章

  1. 复制SharePoint列表项(SPListItem)到另一个列表

    从理论上讲,有一个简单到难以置信的解决办法:SPListItem提供了一个CopyTo(destinationUrl)方法(可参考MSDN).不幸的是,这个方法似乎用不了.至少对我的情况(一个带附件的 ...

  2. 【Flutter 实战】一文学会20多个动画组件

    老孟导读:此篇文章是 Flutter 动画系列文章第三篇,后续还有动画序列.过度动画.转场动画.自定义动画等. Flutter 系统提供了20多个动画组件,只要你把前面[动画核心](文末有链接)的文章 ...

  3. phpcms 列表项 内容项

    根据上一篇内容继续 首页替换完成后 接下来替换列表页 首先把列表的静态网页放入相应模板的content文件夹下,并改名为 list.html 并且创建栏目时选择下面一项 同样,头尾去掉,利用{temp ...

  4. Flutter实战视频-移动电商-17.首页_楼层组件的编写技巧

    17.首页_楼层组件的编写技巧 博客地址: https://jspang.com/post/FlutterShop.html#toc-b50 楼层的效果: 标题 stlessW快速生成: 接收一个St ...

  5. Python3基础 把一个列表中内容给另外一个列表,形成两个独立的列表

    镇场诗:---大梦谁觉,水月中建博客.百千磨难,才知世事无常.---今持佛语,技术无量愿学.愿尽所学,铸一良心博客.------------------------------------------ ...

  6. Flutter 目录结构介绍、入口、自定义 Widget、MaterialApp 组件、Scaffold 组件

    Flutter 目录结构介绍 文件夹 作用 android android 平台相关代码 ios ios 平台相关代码 lib flutter 相关代码,我们主要编写的代 码就在这个文件夹 test ...

  7. Swift - 列表项尾部附件点击响应(感叹号,箭头等)

    列表单元格尾部可以添加各种样式的附件,如感叹号,三角箭头等.而且点击内容区域与点击附件的这两个响应事件是不同的,这样可以方便我们实现不同的功能(比如点击内容则查看详情,点击感叹号则编辑) 1 2 3 ...

  8. CSS中列表项list样式

    CSS列表属性 属性 描述 list-style-属性 用于把所有用于列表的属性设置于一个声明中. list-style-image 将图象设置为列表项标志. list-style-position ...

  9. SharePoint 2010 列表项事件接收器 ItemAdded 的使用方法

    列表项事件处理器是继承于Microsoft.SharePoint.SPItemEventReceiver的类,Microsoft.SharePoint.SPItemEventReceiver类提供了许 ...

随机推荐

  1. 代码模板整理(0):先水一波a+b

    梦开始的地方 "Hello World!" #include<bits/stdc++.h> using namespace std; int main(){ cout& ...

  2. Prometheus 四种metric类型

    Prometheus的4种metrics(指标)类型: Counter Gauge Histogram Summary 四种指标类型的数据对象都是数字,如果要监控文本类的信息只能通过指标名称或者 la ...

  3. 深度学习与CV教程(13) | 目标检测 (SSD,YOLO系列)

    作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/37 本文地址:http://www.showmeai.tech/article-det ...

  4. Camunda如何配置和使用mysql数据库

    Camunda默认使用已预先配置好的H2数据库,数据库模式和所有必需的表将在引擎第一次启动时自动创建.如果你想使用自定义独立数据库,比如mysql,请遵循以下步骤: 一.新建mysql数据库 为Cam ...

  5. Linux-Centos快速安装Docker

    卸载之前的docker sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-l ...

  6. 基恩士的浓淡补正算法(Shading Correction Filter)的模拟实现。

    知道这个算法应该有很久了,主要当时在意2个事情,一个是这个名字的翻译是在是搞笑,第二是这个算法的效果.不过一直以来都十分好奇这个算法是怎么实现的.因为之前一直无法实际的用基恩士的软件平台用不同的图片去 ...

  7. 在Ubuntu系统下,可执行文件的表现形式

    在Windows系统下的可执行文件都带有.exe的后缀,而对于Linux系统下的可执行文件,则不会带有后缀,如下图 对于.txt文件,Ubuntu下也有相应的记事本程序打开,对于.xml,ubuntu ...

  8. Linux文本三剑客-sed

    sed工作原理: sed: Stream Editor.流编辑器 --- 属于行编辑工具 sed和vim一样都是文本编辑工具. 行编辑工具:一行一行处理文件内容 全屏编辑工具:一次性将文件内容加载到内 ...

  9. 【JS】两数之和

    给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是,数组中同一 ...

  10. sql-DQL-单表查询

    单表查询 select [distint]* 字段列表 from 表名列表 where 条件列表 group by 分组字段 having 分组之后的条件 order by 排序 limit 分页限定 ...