Spark_Form

简介

spark form 是一个flutter平台的表单快速成型框架,其内部包括以下几个部分

  • 表单快速构建
  • 表单数据校验及数据管理
  • 自定义数据生成组件
  • 基于主题的自定义样式
  • 开放式组件及自定义接入组件
  • 焦点管理工具
  • 自定义时间日期选择器
  • 自定义选择器

基于上述功能,可以快速进行flutter的表单应用的开发,减少不必要的功能编写

表单数据处理

FormGroupManage

FormGroupManage是用于管理表单数据的生成及管理类,提供具名构造函数create

factory FormGroupManage.create(Map<String, ManageFieldItem> createField) =>
FormGroupManage._(createField);

接收的参数类型为Map<String, ManageFieldItem>其对应每一个字段所使用的组件类型的Option,并由 ManageFieldItem管理,你可以这样构建你的表单数据

final Map<String, ManageFieldItem> _fieldMap = {
  'test1': ManageFieldItem(data: ''),
  'test2': ManageFieldItem(
      data: SparkLabelTitleData.from('hahshdh', 'change value')),
  'test5': ManageFieldItem(data: '2'),
  'select': ManageFieldItem(
    data: List.generate(
      5,
          (index) => SparkTagGroupItem.from(
        name: 'hh$index a',
        data: '$index',
      ),
    ),
  ),
}

因为每一种组件所使用的数据类型不一致,所以在生成的时候,所对应的数据格式也不一致,以对应不同组件的数据生成,组件对应数据格式见每个组件的详细介绍

Tip: FormGroupManage必须生成在form构建之前

内部可以进行表单数据的获取

/// get json data
/// exclude field[excludeField]is not include in json data
Map getJson({List<String>? excludeField}) {
  ....
}

只需要调用此方法,即可得到转换后的数据,类型为Map结构,其对应关系与传入的转换数据一致,用于表单数据的生成

SparkFormDataChangeImp

对于表单构建的数据,如果需要使用自定义数据,请继承实现,类包含两个字段,data为最后转换数据返回的数据,needSelected 为转化依据,只要当值为true时才会被转换

abstract class SparkFormDataChangeImp {
  SparkFormDataChangeImp(this.data);

  /// Serialized data
  /// [FormManage.getJson] use this filed to create json data
  dynamic data;

  /// only true can use by [FormManage.getJson]
  bool needSelected = false;
}

表单基础组件介绍

在基础组件中,应该遵循的封装原则是,组件可脱离form主体进行使用,即可单独使用

SparkForm

表单组件,是一个表单的顶层组件,接收参数为

const SparkForm({
Key? key,
required this.child,
required this.manage,
this.controller,
}) : super(key: key);

/// form controller
final FormController? controller;

/// form item builder
final Widget child;

/// form data manage
final FormGroupManage manage;

在表单构建的时候,组件必须包裹在最外层,以提供SparkFormItem所需要的运行数据,组件接收一个SparkFormController作为内部控制器,可用于表单的校验和清除操作,见表单校验

SparkFormItem

表单子组件。其功能包括 label 布局生成,焦点管理,校验错误内容提示,等功能,接收参数为

const SparkFormItem({
Key? key,
required this.fieldName,
required this.child,
this.direction,
this.labelBuilder,
this.label,
this.isRequired,
this.rules,
this.labelWidth,
this.lablePadding,
this.decoration,
this.readOnly = false,
this.padding,
this.margin,
this.labelStyle,
this.crossCenter,
this.errorStyle,
this.errorContentPadding,
this.labelSuffix,
}) : super(key: key);

/// form item bind field name
final String fieldName;

/// form label name
final String? label;

/// form label widget builder
final WidgetThemeBuilder? labelBuilder;

/// form widget builder
final Widget child;

/// form layout direction, set title layout in top or left
final Direction? direction;

/// form item is required
final bool? isRequired;

/// label width
final double? labelWidth;

/// setup label layout padding
final EdgeInsets? lablePadding;

/// form item decoration
final FocusDecoration? decoration;

/// value check rules
final List<SparkValidator>? rules;

/// set form item response focusNode
/// if false, focus style is not't change
/// [FocusPointerScope.shadow]
final bool? readOnly;

/// form item content padding
final EdgeInsets? padding;

/// form item content margin
final EdgeInsets? margin;

/// set label text style
final TextStyle? labelStyle;

/// cross use [CrossAxisAlignment.center]
final bool? crossCenter;

/// error tip message style
final TextStyle? errorStyle;

/// tip message content layout
final EdgeInsets? errorContentPadding;

/// label suffix, is left label text suffix
/// not form item right suffix
final Widget? labelSuffix;

组件提供丰富的自定义属性及布局方式,支持横行和纵向布局,使用direction来决定其布局方向,如同普通表单一样,在设置isRequired属性后,表单会显示*符号,用来表示其是否为必填项,但是它与内部校验无关,如果需要让该表单项不为空,请使用规则,同时,组件支持传入校验规则,只要在rules属性中加入不同的规则,即可进行不同的校验,见 表单校验, 比较特殊的是,组件内部使用了焦点吸附,表单项被选中时,会进行对应的焦点操作,对于样式的控制,有几个属性,包括readOnly decorationreadOnly设置为true时,焦点获取将不会更改样式,而样式的设置,可以使用decoration属性,见FocusDecoration,label设置时,如果是不需要使用label显示,不设置labellabelBuilder即可,其余属性均与布局有关,如crossCenter用于设置label于表单项的交叉轴对其方式,为true时将为居中对齐,否则使用顶部对齐,对于必须指定的项 fieldName,指每一项对应的表单数据

SparkLabelTitle

SparkLabelTitle为label组件,通常用于选择显示,下拉样式显示等,其样式与flutter的原生组件TitleLabel相似,其提供参数如下

const SparkLabelTitle({
Key? key,
required FormGroupItem manage,
this.suffixIcon,
this.showSuffix,
this.suffixSize,
this.maxLines,
this.onTap,
this.hitText,
this.style,
this.hitTextStyle,
this.padding = EdgeInsets.zero,
this.margin,
this.width,
this.selectDown = false,
this.contentPadding,
}) : super(key: key, manage: manage);

/// custom suffix icon
final Widget? suffixIcon;

/// set show suffix icon
final bool? showSuffix;

/// set suffix icon size
final Size? suffixSize;

/// set text max line count
final int? maxLines;

/// label tap
final SparkLabelTitleData? Function(SparkLabelTitleData data)? onTap;

/// empty hit text
final String? hitText;

/// show text style
final TextStyle? style;

/// hit text style
final TextStyle? hitTextStyle;

/// set body padding
final EdgeInsets padding;

/// set body margin
final EdgeInsets? margin;

final double? width;

/// choose suffix icon type
final bool selectDown;

/// set content and suffix padding
final EdgeInsets? contentPadding;

提供包括文字提示、自定义文字内容显示,内部内容更新采用数据回传模式

final SparkLabelTitleData? Function(SparkLabelTitleData data)? onTap;

如果在onTap传入的函数中返回 SparkLabelTitleData类型的数据,将会把返回的数据更新到显示区域,并显示对应的内容,完成数据展示 组件包含另一个字段 selectDown 字段用于对尾部默认的箭头图标进行替换,如果值为true将显示向下的箭头 ,也可以设置showSuffix来设置是否显示尾部图标,或者suffixIcon来自定义图标

SparkSelect

组件为单选组件,用于选择情景,内部参数定义为

const SparkSelect({
Key? key,
required FormGroupItem manage,
required this.options,
this.itemBuilder,
this.itemSpacing,
this.itemSize,
this.selectedDecoration,
this.unSelectedDecoration,
this.alignment,
}) : super(key: key, manage: manage);

/// option data
final List<SparkSelectOption> options;

/// custom item builder
final ItemValueBuilder? itemBuilder;

/// item space
final double? itemSpacing;

/// set up option size
/// use BoxConstraints
final Size? itemSize;

/// set up select option style
final SparkSelectDecoration? selectedDecoration;

/// set up default option style
final SparkSelectDecoration? unSelectedDecoration;

/// option alignment
final WrapAlignment? alignment;

对于组件来说,需要指定 options内容,用于显示选择的项,数据类型必须为SparkSelectOption,选中的数据为SparkSelectOption.value,会在表单系统中,自动与外层包裹的SparkFormItemfieldName字段所对应的属性对应,并更改其值,在进行自定义时itemBuilder将会把内部设置的样式及数据进行传递

/// spark select item options builder
/// [SparkSelect]
typedef ItemValueBuilder = Widget Function(BuildContext context, SparkOption value, SparkSelectDecoration decoration);

SparkTagGroup

标签选择器,器包含多种选择模式,如单选&添加、多选&添加、单选、多选,并可设置是否清除

const SparkTagGroup({
Key? key,
this.itemBuilder,
required FormGroupItem manage,
this.showClean = false,
this.type = SparkTagGroupType.multiple,
this.spacing = 10,
this.runSpacing = 10,
this.onAdd,
this.addWidget,
this.hitText,
this.hitWidget,
this.hitStyle,
this.alignment,
this.cleanIconBuilder,
}) : super(key: key, manage: manage);

/// custom item builder
final TagGroupItemBuilder? itemBuilder;

/// set up clean icon visible
final bool showClean;

/// group type
final SparkTagGroupType type;

/// see [Wrap]
final double spacing;

/// see [Wrap]
final double runSpacing;

/// group out side add function
final SparkTagGroupItem? Function()? onAdd;

/// costom add widget
final Widget? addWidget;

/// empty hit text
final String? hitText;

/// hit widget
final Widget? hitWidget;

/// hit text style
final TextStyle? hitStyle;

/// see [Wrap]
final WrapAlignment? alignment;

/// custom clean icon builder
final TagGroupItemIconBuilder? cleanIconBuilder;

组件功能和Select类型,但是对于多标签选择和添加时,显得十分方便,已有的几种模式,已经能满足大部分的场景,只需要对不同的属性进行修改,即可达到对于的效果,特殊的地方在于,onAdd可给外部提供添加能力,或者,你也可以直接操作字段对应的数据,来达到添加的目的

SparkTextField

基于flutter本身TextField的封装,舍弃了一些原有的功能,对suffix prefix cleanble进行了封装,使用体验更加友好,其余使用与原生功能几乎一致

数据更新

表单组件的实现是依据与flutter提供的 InheritedWidget 实现,为了方便更新数据,spark提供了便利的获取和更新方案

SparkBeaconNotification

0.0.7版本加入

在spark form 系统中,我们提供了一个'信标',使用方式也很简单,只要在SparkFormItem 包裹的范围内获取BuildContext 即可进行数据操作

Builder(builder: (_) {
return GestureDetector(
onTap: () {
List<SparkTagGroupItem> _data = SparkBeaconGetter.form<List<SparkTagGroupItem>>(_, 'select');
_data.add(SparkTagGroupItem.from(name: 'test'));
SparkBeaconNotification(value: _data, field: 'select').dispatch(_);
},
child: const Icon(
Icons.search,
size: 18,
),
);
}),

上述代码即为一个简单的更新通知,在范围内,使用该类即可实现数据更新

SparkBeaconGetter.form

在表单系统中,为了方便获取数据,还提供了一个数据获取方法,在SparkBeaconGetter中,使用静态方法form即可获取到指定的值的值

static T form<T>(BuildContext context, String field) {
...
}

值得注意的是,field属性可以指定所有的表单定义属性

表单校验

SparkForm组件中,我们提供了一个SparkFormController,传入实例后,便可对表单进行校验操作

SparkFormController _controller = SparkFormController();
...
SparkForm(
controller: _controller,
manage: _manage,
...
)
...

SparkFormController

SparkFormController中,提供了多个校验方法

class SparkFormController {

  ValidateResult validateField(String fieldName, VerifyType type, {dynamic preValue}) {
    ...
  }

  /// validate all rules, get validate result
  /// [focusError] auto focus first error
  ValidateResult validate({bool? focusError}) {
    ...
  }

  /// reset all field data
  void resetAll() {
    _manage?.reset();
  }
}

如果你需要检测表单的正确性,只需要调用SparkFormController.validate方法即可,方法提供自动聚焦到第一项错误的功能,SparkFormController.resetAll则会将表单初始化为初始数据,对应的SparkFormController.validateField方法即是对单独的某一个字段进行验证

SparkValidator

对于表单的验证模块,即对应SparkFormItem中的rules字段,可以对每个属性的值进行验证,对于不同的属性值,验证规则也不同,spark_form内提供多个属性验证器

abstract class SparkValidator {
  SparkValidator({
    required this.errorMessage,
    required this.type,
    this.strategy,
  });

  /// verify error message
  String errorMessage;

  /// validate type
  VerifyType? type;

  /// validate strategy
  ///
  FormStrategy? strategy;

  /// validate rule
  ValidateResult validate(dynamic value, String fieldName, VerifyType? type);
}

每个规则可以对应不同的触发校验的方式,设置type即可,如果需要自己的校验器,继承于SparkValidator实现即可

enum VerifyType {
  /// verfiy value after changed
  change,

  /// verfiy value after blur
  blur,

  /// include change and blur
  all,
}

然而对于strategy字段,则是对规则的行为做出区分

enum FormStrategy {
  /// form change strategy, if valitate error, back to old value
  limit,

  /// just tip error message
  tip,

  /// Not to do anything
  noop,
}

规则设置为FormStrategy.limit时,值将会限制输入,无法变成不符合规则的值,而FormStrategy.tip则会触发表单的提示功能,最后一个属性则是不做任何事,用于区分校验策略 校验规则也各有不同,

SparkLengthValidator

提供一个字符串长度验证器

SparkLengthValidator.length(
this.length, {
required String errorMessage,
required VerifyType? type,
FormStrategy? strategy = FormStrategy.limit,
}) : super(
errorMessage: errorMessage,
type: type,
strategy: strategy,
);

可以对属性值的长度进行验证(必须为字符串)

SparkFormatValidator

提供一个正则验证器

SparkFormatValidator.form(
this.rule, {
required String errorMessage,
required VerifyType? type,
FormStrategy? strategy = FormStrategy.limit,
}) : super(
errorMessage: errorMessage,
type: type,
strategy: strategy,
);

可对属性值的格式进行验证(必须为字符串)

SparkNumberValidator

提供一个数字格式验证器,包含多种验证方式,包括字符格式正确性验证、大小范围验证、整数和小数长度验证,通过不同具名构造函数构建即可使用

后者的验证建立在数字格式的基础上进行,如果需要更加准确的提示,应该先使用number验证字符格式

 /// number only
SparkNumberValidator.number({
required String errorMessage,
required VerifyType? type,
FormStrategy? strategy = FormStrategy.limit,
}) : super(
errorMessage: errorMessage,
type: type,
strategy: strategy,
);

/// setup number range
SparkNumberValidator.range({
required this.max,
required this.min,
required String errorMessage,
required VerifyType? type,
FormStrategy? strategy = FormStrategy.limit,
})  : assert(
max > min,
'SparkNumberValidator.range max must be max than min',
),
super(
errorMessage: errorMessage,
type: type,
strategy: strategy,
);

/// setup number integer part length and decimal part length
SparkNumberValidator.length({
required this.integerLength,
required this.decimalLength,
required String errorMessage,
required VerifyType? type,
FormStrategy? strategy = FormStrategy.limit,
}) : super(
errorMessage: errorMessage,
type: type,
strategy: strategy,
);

对于校验值,也只能为字符串

SparkRequiredValidator

提供一个非空验证器

SparkRequiredValidator.required({
required String errorMessage,
required VerifyType? type,
FormStrategy? strategy = FormStrategy.limit,
}) : super(
errorMessage: errorMessage,
type: type,
strategy: strategy,
);

兼容已有数据结构

主题

对于spark_form来说。大部分情况下都可以通过组件上的自定义属性来更改不同的样式,但是有没有一种一劳永逸的方式来更改同表单内的样式呢?答案的存在的 在spark_form中存在一个组件SparkThemeData,其实现类似于flutter的ThemeData实现

class SparkThemeData extends InheritedWidget {
  /// form theme data manage provider
  const SparkThemeData({required Widget child, required this.theme, Key? key})
      : super(child: child, key: key);

  /// form theme
  final SparkTheme theme;

  /// get form theme data of context
  static SparkTheme? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<SparkThemeData>()?.theme;
  }

  @override
  bool updateShouldNotify(covariant InheritedWidget oldWidget) {
    return true;
  }
}

通过继承InheritedWidget实现一个数据共享中心,其包裹的组件都可以通过SparkThemeData.of获取其内部存储的theme,基于flutter提供的特性,我们实现了一套主题系统,你可以通过设置主题的方式,来初始化全局的表单样式,只需要将表单组件作为SparkThemeData的字组件即可,例如

SparkThemeData(child: SparkForm(
controller: _controller,
manage: _manage,
child: ...
), theme: SparkTheme(),)

即可对一系列的样式进行初始化定义,方便统一的风格和主题的定制,SparkTheme目前支持的属性为

const SparkTheme({
this.labelColor = _defaultFontColor,
this.hitColor = _hitTextColor,
this.labelFontSize = _defaultFormItemFontSize,
this.labelFontWeight = FontWeight.w500,
this.requiredColor = _errorOrRequiredColor,
this.requiredPrefix = '*',
this.formItemFontSize = _defaultFormItemFontSize,
this.formItemColor = _defaultFormItemColor,
this.formItemDisabledColor = _defaultFormItemDisabledColor,
this.lableWidth = _defaultLabelWidth,
this.lablePadding = EdgeInsets.zero,
this.formItemDecoration = _defaultFormDecoration,
this.formItemPadding = _defaultFormItemPadding,
this.formItemMargin = _defaultFormItemMargin,
this.selectItemSize = _defaultSelectItemSize,
this.selectedItemDecoration = _defaultSelectedDecoration,
this.unSelectedItemDecoration = _defaultUnSelectedDecoration,
this.errorColor = _errorOrRequiredColor,
this.tagItemDecoration = _defaultTagDecoration,
this.tagSelectedItemDecoration = _defaultTagSelectedDecoration,
this.tagStyle = _defaultTagItemStyle,
this.tagSelectedStyle = _defaultSelectedItemStyle,
this.tagMinWidth = _defaultTagWidth,
this.tagAddDecoration = _defaultTagAddDecoration,
this.tagContentPadding = _defaultTagContentPadding,
});

主题和组件本身的样式并不冲突,如果组件内部设置了对应样式,将不会使用主题样式

目前主题功能还处于深度定制阶段,一些属性还未列为主题管理

焦点吸附组件

在spark_form中,提供了一个焦点吸附组件 FocusPointerScope,其实现是基于flutter的焦点管理体系的,在进行表单选择切换时,会自动进行焦点的获取,对于其内部实现为独立封装,可用于其他功能的实现,内部吸附的组件为一个Container可对焦点的响应做出各种行为,并暴露相应的事件

class FocusPointerScope extends StatefulWidget {
  const FocusPointerScope({
    Key? key,
    this.focusNode,
    required this.child,
    this.shadow = false,
    this.enabled,
    this.decoration,
    this.padding,
    this.margin,
    this.alignment,
    this.constraints,
    this.onTap,
    this.onFocus,
    this.onUnFocus,
    this.width,
    this.height,
  }) : super(key: key);

  final FocusNode? focusNode;

  final Widget child;

  /// only request focus,but not set focus style
  final bool? shadow;

  /// focus enable
  final bool? enabled;

  /// widget decoration
  final FocusDecoration? decoration;

  /// content padding
  final EdgeInsets? padding;

  /// content margin
  final EdgeInsets? margin;

  final AlignmentGeometry? alignment;

  final BoxConstraints? constraints;

  /// on widget tap
  final VoidCallback? onTap;

  /// on focus
  final VoidCallback? onFocus;

  /// on lose focus
  final VoidCallback? onUnFocus;

  final double? width;

  final double? height;

  @override
  State<StatefulWidget> createState() => _FocusPointerScopeBase();
}

其实现原理的:通过Listener进行事件监听,得到内部点击事件,然后进行焦点的获取

_handleTap() {
  ...
  FocusScope.of(context).requestFocus(_effectiveFocusNode);
}

@override
void initState() {
  _effectiveFocusNode.addListener(_handleFocusChanged);
  // attach focusNode target widget
  _focusAttachment = _effectiveFocusNode.attach(context);
  super.initState();
}

@override
void didUpdateWidget(covariant FocusPointerScope oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (widget.focusNode != oldWidget.focusNode) {
    (oldWidget.focusNode ?? _focusNode)?.removeListener(_handleFocusChanged);
    (widget.focusNode ?? _focusNode)?.addListener(_handleFocusChanged);
    // replace focusNode, rebind context
    _focusAttachment?.detach();
    _focusAttachment = _effectiveFocusNode.attach(context);
  }
  _effectiveFocusNode.canRequestFocus = _canRequestFocus;
}

@override
void dispose() {
  _effectiveFocusNode.removeListener(_handleFocusChanged);
  _focusNode?.dispose();
  _focusAttachment?.detach();
  super.dispose();
}

由此可以得到一个焦点管理的实现,避免了焦点混乱

自定义表单组件

对于自定义表单组件,我们提供了两种封装思路

组件形式封装

基于组件的形式进行封装的话,组件应该对应以下类进行对应的继承实现

abstract class SparkFormStateLessWidget<D> extends StatelessWidget {
  const SparkFormStateLessWidget({Key? key, required this.manage}) : super(key: key);

  final FormGroupItem manage;

  D get fieldData => manage.notifier.data;

  void handleFormDataChange(BuildContext context, dynamic newValue) {
    FormDataChangeNotification(
      newValue: newValue,
    ).dispatch(context);
  }
}

abstract class SparkFormWidget extends StatefulWidget {
  const SparkFormWidget({Key? key, required this.manage}) : super(key: key);

  final FormGroupItem manage;
}

abstract class SparkFormWidgetBase<T extends SparkFormWidget, D> extends State<T> {

  D get fieldData => widget.manage.notifier.data;

  FormGroupItem get manage => widget.manage;

  void handleFormDataChange(dynamic newValue) {
    FormDataChangeNotification(
      newValue: newValue,
    ).dispatch(context);
  }

  @override
  void initState() {
    widget.manage.notifier.addListener(valueChangeNotify);
    super.initState();
  }

  void valueChangeNotify();

  @override
  void dispose(){
    widget.manage.notifier.removeListener(valueChangeNotify);
    super.dispose();
  }

}

分别对应有状态组件和无状态组件的继承实现,基类中存在方法handleFormDataChange,只需要将组件产生的新值通过此方法进行传递,form就可以处理到对应的项,然后在 valueChangeNotify进行回调执行,也就是校验完成后会执行此方法,可用于组件的刷新和其他处理

组件包裹

如果不需要使用到组件形式的实现,可以使用SparkWrapper来进行组件的包裹,但是需要注意的是,该形式无法触发表单的验证功能,仅仅提供表单内部值发生变化时的更新功能,实现方式是基于数据监听的方法实现

const SparkWrapper({
Key? key,
required this.builder,
required FormGroupItem manage,
}) : super(key: key, manage: manage);

只需要将你自己的组件包裹在其中,即可得到更新值和自定刷新的能力,缺点是不存在组件缓存,不建议消耗过大的组件使用此方法进行处理

自定义ModalSheet

在组件内部,实现了一个基于Navigator的 modalSheet,相对于flutter提供的showModalBottomSheet来说,去除了布局的最大高度约束,元素大小及位置布局相对自由

Future<T?> showSparkModalSheet<T>({
  required BuildContext context,
  required WidgetBuilder builder,
  ShapeBorder? shape,
  bool useRootNavigator = false,
  Clip? clipBehavior,
}) {
  final NavigatorState navigator =
  Navigator.of(context, rootNavigator: useRootNavigator);
  return navigator.push(_SparkModalRoute<T>(
    builder: builder,
    shape: shape,
    clipBehavior: clipBehavior,
    barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
  ));
}

调用showSparkModalSheet方法即可创建一个路由弹出框,内部视图可自定义实现,但是值得注意的是,内部元素必须有自身的高度,对于约束模型来说,如果内部元素没有大小约束,将会占满整个试图,即最大高宽,如果需要pop掉视图,应该使用builder给定的context来操作,如

Navigator.of(dialogContext).pop();

即可让modalSheet消失

自定义3D滚轮选择器

组件是基于CupertinoPicker进行封装,对于滚轮的各个阶段事件进行分发

class SparkCommonPicker extends StatelessWidget {
  const SparkCommonPicker({
    Key? key,
    required this.controller,
    required this.itemHeight,
    required this.itemBuilder,
    required this.itemCount,
    this.onChanged,
    this.onChange,
    this.onStart,
    this.selectionOverlay,
  }) : super(key: key);

  /// picker controller
  final FixedExtentScrollController controller;

  /// single option height
  final double itemHeight;

  /// pick option builder
  final SparkPickerBuilder itemBuilder;

  /// item count
  final int itemCount;

  /// on changed, get last result
  final SparkPickerChanged? onChanged;

  /// scroll animation change index, get scrolling index value
  final SparkPickerChanged? onChange;

  final Function? onStart;

  /// selection overlay widget
  final Widget? selectionOverlay;
}

组件SparkCommonPicker是一个单独的存在,也就是说,如果你需要使用一个弹出选择框,你需要将 showSparkModalSheet与其组合使用,即可成为一个简单的选择组件

SparkPicker

组件是基于SparkCommonPicker的封装,可以直接用于使用

class SparkPicker<T> extends StatelessWidget {
  const SparkPicker({
    required this.options,
    this.itemHeight = 40,
    this.itemBuilder,
    this.style,
    this.controller,
    Key? key,
  }) : super(key: key)

showSparkMultiplePicker

spark内部提供了一个多列滑动选择器,选择器内部可以进行多列数据的选择

Future<Map<String, T>?> showSparkMultiplePicker<T>({
  required BuildContext context,
  required Map<String, List<SparkPickerData<T>>> options,
  double itemHeight = 40,
  SparkPickerBuilder? itemBuilder,
  bool showBar = true,
  String cancelText = '取消',
  String confirmText = '确定',
  TextStyle? cancelStyle,
  TextStyle? confirmStyle,
  double height = 300,
  SparkPickerTitleBarBuilder? barBuilder,
  Map<String, T>? defaultValue,
}) 

对于选择器来说,options字段需要设置指定的key,在最后返回的数据中,会返回对应的数据字段的Map集合,同样,对于defaultValue也是需要对于的,如果有其他非必需属性,会被忽略,不会进行使用

showSparkSingePicker

单选选择器是一个基于showSparkMultiplePicker的封装,内部对数据格式进行了一个转换,只需要传入单个数据集合即可进行选择操作

Future<T?> showSparkSingePicker<T>({
  required BuildContext context,
  required List<SparkPickerData<T>> options,
  double itemHeight = 40,
  SparkPickerBuilder? itemBuilder,
  bool showBar = true,
  String cancelText = '取消',
  String confirmText = '确定',
  TextStyle? cancelStyle,
  TextStyle? confirmStyle,
  double height = 300,
  SparkPickerTitleBarBuilder? barBuilder,
  T? defaultValue,
}) 

使用方式和 showSparkMultiplePicker基本一致,参数也基本一致

时间日期选择器

时间日期选择器SparkDatePicker提供了很多非常强大的功能,组件可单独使用,如果需要进行弹出选择,可以使用提供的函数

Future<T?> showSparkDatePicker<T>({
  required BuildContext context,
  double height = 260,
  DateTime? defaultDate,
  DateTime? maxDate,
  DateTime? minDate,
  double itemHeight = 40,
  DatePickerTheme theme = _defaultDatePickerTheme,
  DatePickerBehavior behavior = DatePickerBehavior.confirm,

  /// [SparkDateFormat] format
  String? format,
  ShapeBorder shape = _defaultPickerShape,
}) {
  assert(T == String || T == DateTime, 'date picker must setup return type');
  return showSparkModalSheet<T>(
    context: context,
    shape: shape,
    builder: (_) => SparkDatePicker<T>(
      defaultDate: defaultDate,
      height: height,
      theme: theme,
      minDate: minDate,
      maxDate: maxDate,
      itemHeight: itemHeight,
      behavior: behavior,
      format: format ?? SparkDateFormat.yearMonthDayAndHourMinuteSecond,
      onChanged: (_value) {
        Navigator.of(_).pop(_value);
      },
    ),
  );
}

对于函数showSparkDatePicker来说,需要制定泛型T的类型,目前支持的类型为String DateTime,这将作为时间选择器的内部数据返回格式类型,函数的返回结果和泛型类型一致,对于参数来说,可以指定默认的时间日期和最大最小日期

  /// default date
  final DateTime? defaultDate;

  /// min date
  final DateTime? minDate;

  /// max date
  final DateTime? maxDate;

设置参数后只能在固定范围内选择,否则生成的选择范围为20 <- now -> 30,选择器还提供了一个更加方便的功能,更具时间格式来选择对应的时间或日期字段,然后返回对应的结构(String模式下),对应的为设置 format字段,其值为

class SparkDateFormat {
  /// use part:
  /// 'yyyy' year
  /// 'MM' month
  /// 'dd' day
  /// 'hh' hour
  /// 'mm' minute
  /// 'ss' second
  SparkDateFormat._();

  static const String _yearPart = 'yyyy';

  static const String _monthPart = 'MM';

  static const String _dayPart = 'dd';

  static const String _hourPart = 'hh';

  static const String _minutePart = 'mm';

  static const String _secondPart = 'ss';

  /// date format as '2020-01'
  static const String yearMonth = 'yyyy-MM';

  /// date format as '2020/01'
  static const String yearMonthObliqueLine = 'yyyy/MM';

  /// date format as '2020-01-01'
  static const String yearMonthDay = 'yyyy-MM-dd';

  /// date format as '2020/01/01'
  static const String yearMonthDayObliqueLine = 'yyyy/MM/dd';

  /// time format as '12:01'
  static const String hourMinute = 'hh:mm';

  /// time format as '12:01:59'
  static const String hourMinuteSecond = 'hh:mm:ss';

  /// date time format as '2021-12-01 12:01'
  static const String yearMonthDayAndHourMinute = 'yyyy-MM-dd hh:mm';

  /// date time format as '2021/12/01 12:01'
  static const String yearMonthDayObliqueLineAndHourMinute = 'yyyy/MM/dd hh:mm';

  /// date time format as '2021-12-01 12:01:59'
  static const String yearMonthDayAndHourMinuteSecond = 'yyyy-MM-dd hh:mm:ss';

  /// date time format as '2021/12/01 12:01:59'
  static const String yearMonthDayObliqueLineAndHourMinuteSecond =
      'yyyy/MM/dd hh:mm:ss';
}

其中_XXXPart为解析合法字段,合法分隔符为/ : 三种,对应的格式对应显示对应的选择列

不建议使用自定义的排列方式,因为这可能让其功能出现异常