1. 概述
1.1 关于Widgets Easier
本库是一个 Flutter 组件库,旨在提供用于Flutter开发的组件,使得开发者能够更简单地构建出更丰富地界面效果。项目地址为:
1.2 模块安装
在你的Flutter项目中,运行下面的命令:
flutter pub add widgets_easier
即可安装最新版本的 Widgets Easier 库。
2. 基本用法
2.1 语义类型
通过Tag组件的type
参数可以使用一个语义性色彩。例如:
const Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Tag('tag', type: SemanticEnum.primary), Tag('tag', type: SemanticEnum.secondary), Tag('tag', type: SemanticEnum.info), Tag('tag', type: SemanticEnum.success), Tag('tag', type: SemanticEnum.warning), Tag('tag', type: SemanticEnum.danger), Tag('tag', type: SemanticEnum.fatal), ], ),
2.2 样式主题
受启发与Element-plus,Tag有3个样式主题,plain
、light
和dark
,默认情况下为palin
,正如上一节所展示的那样。下面展示light
和dark
两个主题:
light
const Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Tag('tag', theme: TagThemeEnum.light, type: SemanticEnum.primary), Tag('tag', theme: TagThemeEnum.light, type: SemanticEnum.secondary), Tag('tag', theme: TagThemeEnum.light, type: SemanticEnum.info), Tag('tag', theme: TagThemeEnum.light, type: SemanticEnum.success), Tag('tag', theme: TagThemeEnum.light, type: SemanticEnum.warning), Tag('tag', theme: TagThemeEnum.light, type: SemanticEnum.danger), Tag('tag', theme: TagThemeEnum.light, type: SemanticEnum.fatal), ], ),
dark
const Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Tag('tag', theme: TagThemeEnum.dark, type: SemanticEnum.primary), Tag('tag', theme: TagThemeEnum.dark, type: SemanticEnum.secondary), Tag('tag', theme: TagThemeEnum.dark, type: SemanticEnum.info), Tag('tag', theme: TagThemeEnum.dark, type: SemanticEnum.success), Tag('tag', theme: TagThemeEnum.dark, type: SemanticEnum.warning), Tag('tag', theme: TagThemeEnum.dark, type: SemanticEnum.danger), Tag('tag', theme: TagThemeEnum.dark, type: SemanticEnum.fatal), ], ),
2.3 圆角
默认有一个大小为4的圆角,若要手动修改可以通过指定radius参数。radius参数接受一个double值。例如,设置radius为0则没有圆角:
Tag('radius: 0', radius: 0),
2.4 尺寸
枚举尺寸
例如:
Tag('SizeEnum.small', size: SizeEnum.small), Tag('SizeEnum.defaultSize', size: SizeEnum.defaultSize), Tag('SizeEnum.large', size: SizeEnum.large),
数值尺寸
通过height参数可以指定数值作为尺寸。height一经指定,则size失效。例如,指定高度为50:
Tag('hignt=50', height: 50),
收缩属性
通过指定shrink属性为flase,可以使一个标签尽可能占满一行,例如:
Tag('shrink: false', shrink: false)
2.5 可关闭标签
通过指定 closable: true,将展示一个关闭图标。例如:
const Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Tag('tag', closable: true, type: SemanticEnum.primary), Tag('tag', closable: true, type: SemanticEnum.secondary), Tag('tag', closable: true, type: SemanticEnum.info), Tag('tag', closable: true, type: SemanticEnum.success), Tag('tag', closable: true, type: SemanticEnum.warning), Tag('tag', closable: true, type: SemanticEnum.danger), Tag('tag', closable: true, type: SemanticEnum.fatal), ], ),
2.6 动态编辑标签示例
可以通过点击标签关闭按钮后触发的 onClose 事件来实现动态编辑标签。例如:
import 'package:flutter/material.dart'; import 'package:widgets_easier/widgets_easier.dart'; class DynamicTagsExample extends StatefulWidget { const DynamicTagsExample({super.key}); @override State<DynamicTagsExample> createState() => _DynamicTagsExampleState(); } class _DynamicTagsExampleState extends State<DynamicTagsExample> { final List<String> _tags = ['Tag 1', 'Tag 2', 'Tag 3']; final String _newTagButtonText = '+ 添加 Tag'; void _handleClose(int index) { setState(() { _tags.removeAt(index); }); } void _handleSubmitted(String value) { if (value.isNotEmpty) { setState(() { _tags.add(value); }); } } @override Widget build(BuildContext context) { return Wrap( spacing: 8, runSpacing: 8, children: [ Text('$_tags'), for (int index = 0; index < _tags.length; index++) Tag( key: UniqueKey(), // 重要 _tags[index], type: SemanticEnum.primary, theme: TagThemeEnum.light, closable: true, onClose: (_) { _handleClose(index); }, ), Tag( _newTagButtonText, type: SemanticEnum.danger, editable: true, restoreAfterSubmitted: true, onSubmitted: _handleSubmitted, ), ], ); } }
果如下:
另外,这个例子恰好是一定要指定key的例子,顺便说一下一种错误情况。
在没有使用 key 的情况下,Flutter 在构建组件树时,会根据组件的位置来匹配新旧组件。当删除一个 Tag 时,其后面的 Tag 会向前移动,占据被删除的 Tag 的位置。但是,Flutter 并不知道这种位置的变化,它仍然认为在原来的位置上的 Tag 与之前的 Tag 相同,导致视图没有正确更新。
这里,初始的 _tags 列表为 ['Tag 1', 'Tag 2', 'Tag 3']
,不断点击第一个Tag的close图标,看起来效果就成了这样:
在这个过程中:
Flutter 根据这个列表构建了三个 Tag 组件,它们在组件树中的位置分别为 0, 1, 2;
现在,如果你删除了 ‘Tag 1’,_tags 列表变为 ['Tag 2', 'Tag 3'];
Flutter 在更新组件树时,会比较新的组件列表与旧的组件列表:
在位置 0,它发现新的组件列表中有一个 Tag 组件,显示 ‘Tag 2’。但在旧的组件列表中,这个位置显示的是 ‘Tag 1’。因为 Flutter 使用位置来匹配组件,所以它认为这个 Tag 组件没有变化,仍然显示 ‘Tag 1’;
在位置 1,它发现新的组件列表中有一个 Tag 组件,显示 ‘Tag 3’。同样,因为位置匹配,Flutter 认为这个 Tag 组件没有变化,仍然显示 ‘Tag 2’;
在位置 2,它发现新的组件列表中没有 Tag 组件了,所以它会移除这个位置的 Tag 组件。
最终,视图中显示的 Tag 组件是 ‘Tag 1’ 和 ‘Tag 2’,而不是 ‘Tag 2’ 和 ‘Tag 3’。
后面再次点击时类似。这就导致了在没有使用 key 的情况下,删除操作会导致视图与实际数据不一致。