前言
学习Flutter有一段时间了,一直想尝试开发一个组件,最近正好结合项目所需,准备自己开发一个条件联动选择器的功能,并将它封装成组件发布到pub.dev
开发组件
需求描述
点击某个功能按钮,实现底部弹窗,数据填充后,item纵向滚动,可以支持单选和多选,以及多选联动的效果。弹窗顶部展示取消和完成按钮,点击完成后,拿到选中的数据。
功能梳理
1.底部弹窗实现
2.定义实体数据
3.列表滚动实现
4.组件封装实现
功能实现
底部弹窗实现
说起底部弹窗,那首先会想到的是showModalBottomSheet,它可以很方便的帮我们实现底部弹窗
Future<T?> showModalBottomSheet<T>({
required BuildContext context,
required WidgetBuilder builder,
Color? backgroundColor,
double? elevation,
ShapeBorder? shape,
Clip? clipBehavior,
BoxConstraints? constraints,
Color? barrierColor,
bool isScrollControlled = false,
bool useRootNavigator = false,
bool isDismissible = true,
bool enableDrag = true,
RouteSettings? routeSettings,
AnimationController? transitionAnimationController,
Offset? anchorPoint,
})
常规属性说明:
- context:上下文,必传参数
- builder:构建内容,必传参数
- backgroundColor:背景颜色
- elevation:阴影高度,没有实际效果
- shape:边框形状,例如设置圆角
- barrierColor:蒙层颜色
- isDismissible:点击外部区域是否关闭弹窗
- enableDrag:拖拽是否支持关闭弹窗
定义实体数据
我们需要定义这样一个实体类,它应该需要包含一个能识别的id、展示的名称、选中的状态、是否支持全选状态、是否支持需要隐藏下一级栏位、联动的子数据集合等参数,例如:
class SelectorItem<T> {
String _id = '';
String _name = '';
bool _hideNext = false;
List<SelectorItem>? _childList;
T? _param;
String get id => _id;
String get name => _name;
bool get hideNext => _hideNext;
List<SelectorItem>? get childList => _childList;
T? get param => _param;
bool check = false;
bool supportSelectAll = false;
SelectorItem(
{required String id,
required String name,
List<SelectorItem>? childList,
bool hideNext = false,
bool isCheck = false,
bool isSupportSelectAll = false,
T? param}) {
_id = id;
_name = name;
_hideNext = hideNext;
_childList = childList;
check = isCheck;
supportSelectAll = isSupportSelectAll;
_param = param;
}
SelectorItem.init();
@override
String toString() {
return 'id:$id name:$name';
}
}
列表滚动实现
要说列表滚动,首先想到的可能是listview,但是选择器这种有没有像ios风格的齿轮滚动的选择器呢?答案肯定是有的,我们可以基于CupertinoPicker组件来实现
CupertinoPicker({
super.key,
this.diameterRatio = _kDefaultDiameterRatio,
this.backgroundColor,
this.offAxisFraction = 0.0,
this.useMagnifier = false,
this.magnification = 1.0,
this.scrollController,
this.squeeze = _kSqueeze,
required this.itemExtent,
required this.onSelectedItemChanged,
required List<Widget> children,
this.selectionOverlay = const CupertinoPickerDefaultSelectionOverlay(),
bool looping = false,
})
常规属性说明:
- diameterRatio:直径比,控制子widget上下偏移
- backgroundColor:背景颜色
- offAxisFraction:轴偏移,控制子widget左右偏移
- useMagnifier:是否使用放大镜效果
- magnification:放大镜倍数
- scrollController:控制器,例如:可以用来初始化选中位置
- squeeze:挤压,控制子widget上下偏移
- itemExtent:子widget的高度范围
- onSelectedItemChanged:滚动回调,会把当前选中的子widget的位置返回
- children:子widget集合
- selectionOverlay:选中样式,可以自定义widget实现
- looping:是否支持循环
组件封装实现
现在底部弹窗和选择器滚动的实现我们已经了解的差不多了,接下来我们只需要实现里面的逻辑交互、数据联动、数据回传、参数自定义等功能即可,然后把它封装成一个功能组件出来。由于封装实现代码量较多,这里就不展开细述了,如需了解请前往flutter_selector,多级联动的核心实现:
// 通过循环调用_addLevel函数,添加子widget
List<Widget> children = [];
_addLevel(List<SelectorItem>? selectorItems,
FixedExtentScrollController? controller, int index) {
// 如果数据不为空,才添加视图
if (null != selectorItems) {
children.add(Expanded(
child: CupertinoPicker(
backgroundColor: Colors.white,
useMagnifier: true,
scrollController: controller,
diameterRatio: widget.diameterRatio ?? 1,
offAxisFraction: widget.offAxisFraction ?? 0.0,
magnification: widget.magnification ?? 1.2,
squeeze: widget.squeeze ?? 1.45,
selectionOverlay: widget.selectionOverlay ??
getSelectionOverlayWidget(
index == 0 ? widget.padding : 0,
index == (widget.list.length - 1) ? widget.padding : 0,
widget.lineColor),
itemExtent: widget.itemExtent,
onSelectedItemChanged: (position) {
for (int i = 0; i < 4; i++) {
if (i == index) {
_refreshData(i, position, false);
} else if (i > index) {
_refreshData(i, 0, true);
}
}
setState(() {});
},
children:
getChildren(selectorItems, widget.textSize, widget.textColor),
)));
}
}
// 联动处理,刷新数据
_refreshData(int level, int position, bool jump) {
switch (level) {
case 0:
_position0 = position;
_selectorItem0 = widget.list[position];
if (jump) {
_controller0?.jumpToItem(position);
}
break;
case 1:
_position1 = position;
_selectorItem1 = _selectorItem0?.childList?[position];
if (jump) {
_controller1?.jumpToItem(position);
}
break;
case 2:
_position2 = position;
_selectorItem2 = _selectorItem1?.childList?[position];
if (jump) {
_controller2?.jumpToItem(position);
}
break;
case 3:
_position3 = position;
_selectorItem3 = _selectorItem2?.childList?[position];
if (jump) {
_controller3?.jumpToItem(position);
}
break;
}
}
发布组件
制作组件
File > New > New Flutter project > next > Project type: package
-
Flutter Application: 编写标准的Flutter App工程
-
Flutter Module : 用于混编,与原生混合开发
-
Flutter Package:纯Dart组件,仅包含Dart层的实现
-
Flutter Plugin:插件工程,包含Dart层与Native平台层的实现
创建好依赖库之后,就可以编写项目代码了。发布包时,遵循pubspec 格式和 包布局约定
必须包含一个LICENSE文件,推荐Dart 和 Flutter 团队通常使用的BSD 3-clause license
编写好readme.md文件,然后将项目发布到pub.dev/
发布组件
创建发布者
点击 Create publisher 创建发布者
使用经过验证的发布者的优势
您可以使用经过验证的发布者(推荐)或独立的 Google 帐户发布包。
使用经过验证的发布者具有以下优势:
- 您的包的消费者知道发布者域已经过验证。
- 您可以避免让 pub.dev 显示您的个人电子邮件地址。相反,pub.dev 显示发布者域和联系地址。
- 在搜索页面和单个包页面上,经过验证的发布者徽章
会显示在您的包名称旁边。
创建经过验证的发布者
要创建经过验证的发布者,请执行以下步骤:
- 转到pub.dev。
- 使用 Google 帐户登录 pub.dev。
- 在右上角的用户菜单中,选择Create Publisher。
- 输入要与发布者关联的域名(例如 dart.dev),然后单击创建发布者。
- 在确认对话框中,选择OK。
- 如果出现提示,请完成验证流程,这将打开Google Search Console。
-
- 添加 DNS 记录时,Search Console 可能需要几个小时才能反映更改。
- 验证流程完成后,返回步骤 4。
发布你的包
使用该dart pub publish命令首次发布您的包,或将其更新到新版本。
发布成功之后,可以登录账号查看已发布的插件
上传项目到GitHub
步骤 1: 在 GitHub 上创建一个新的仓库
- 登录到您的 GitHub 帐户。
- 在页面右上角,点击加号符号(+),然后选择“New repository”。
- 输入仓库名称、描述等信息,然后点击“Create repository”。
步骤 2: 在本地初始化 Git 仓库并关联到 GitHub 仓库
打开终端(Terminal)或命令行界面,进入本地项目文件夹,使用以下命令初始化本地 Git 仓库:
git init
把当前分支重命名为master
git branch -M master
使用以下命令将本地 Git 仓库关联到 GitHub 上创建的远程仓库
git remote add origin <repository_URL>
步骤 3: 添加文件到本地仓库并提交更改
使用以下命令将所有文件添加到暂存区:
git add .
使用以下命令提交更改到本地仓库,并添加提交信息:
git commit -m "Initial commit"
步骤 4: 将本地更改推送到 GitHub
使用以下命令将本地更改推送到 GitHub 上的远程仓库:
git push -u origin master
总结
可以通过不断学习和积累,可以结合自身业务实现一些组件的封装,减少重复性工作。flutter_selector