原因

公司需求开发app项目,可是没有多余的flutter工程师,只能让咱们的前端组去做flutter,根据java的开发经历快速的上手dart,可是一些开发思路仍是偏前端,所以写了一套偏向于前端的flutter表单计划。

规划思路

给予之前写的element-plus的项目的表单经历,我将整个表单分成了两部分一个是用于承载组件的FormItem,另外一部分则是组件本身,给予规划咱们需求支撑的组件目前有输入框、多行文本、挑选框、tag挑选框、头像上传、开关等组件,这儿咱们只介绍一下输入框和挑选框的规划思路,其他的组件规划是类似的。

承载组件的FormItem的规划

首要这儿咱们是需求支撑用户传递他需求的字段的即label,然后关于布局方法咱们也需求支撑,在移动端有两种常见的布局方法关于表单项。

用element-plus的思想去封装一个flutter的表单方案

第二步item要支撑对不同类型组件的烘托,这儿咱们能够用枚举来解决,比如咱们支撑inupt,select这两种类型。

第三步就是根据不同组件类型来烘托不同的组件。

// ignore_for_file: curly_braces_in_flow_control_structures
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:ventora/components/form/input_text.dart';
import 'package:ventora/components/form/select.dart';
enum FormType { input, select }
class FormItem<T> extends StatelessWidget {
  const FormItem(
      {super.key,
      required this.type,
      this.lable,
      this.value,
      this.setValue,
      this.backgroundColor,
      this.lableColor,
      this.buildItem,
      this.lableWidth,
      this.rowCrossAxisAlignment,
      this.margin,
      this.hintText,
      this.selectList,
      this.keyboardType,
      this.valueList,
      this.onClick,
      this.padding,
      this.isSameLine = true,
      this.controller,
      this.maxLength,
      this.textHeight,});
  final FormType type;
  final String? lable;
  final T? value;
  final Function? setValue;
  final Color? backgroundColor;
  final Color? lableColor;
  final Widget? Function()? buildItem;
  final double? lableWidth;
  final CrossAxisAlignment? rowCrossAxisAlignment;
  final EdgeInsetsGeometry? margin;
  final List<String>? selectList;
  final String? hintText;
  final TextInputType? keyboardType;
  final Function? onClick;
  final List<String>? itemList;
  final bool isSameLine;
  final EdgeInsets? padding;
  final TextEditingController? controller;
  final int? maxLength;
  final double? textHeight;
  final double? size;
  Widget initExpanded(Widget child) => isSameLine ? Expanded(child: child) : child;
  _formBuild() {
    if (type == FormType.input)
      return Expanded(
          child: InputText(
              setValue: setValue as Function(String)?,
              controller: controller ?? TextEditingController(text: (value as Rx<String>).value),
              hintText: hintText,
              textHeight: textHeight,
              enabled:enabled,
              contentPadding:contentPadding,
              keyboardType: keyboardType));
    if (type == FormType.select) return Expanded(child: Select(value: value as String, setValue: setValue as Function, selectList: selectList!));
  }
  List<Widget> _listBuild() {
    return [
      if (lable != null)
        Container(
          width: lableWidth ?? 88,
          alignment: Alignment.topLeft,
          margin: margin ?? EdgeInsets.zero,
          child: Text(lable!,
              style: TextStyle(
                fontSize: 15,
                fontWeight: FontWeight.w600,
                color: lableColor ?? Colors.white,
              )),
        ),
      _formBuild()
    ];
  }
  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: padding ?? const EdgeInsets.only(top: 12, bottom: 12, left: 16, right: 16),
      decoration: BoxDecoration(
        borderRadius: const BorderRadius.all(Radius.circular(12.0)),
        color: backgroundColor ?? ColorsLandon.backgroundHome,
      ),
      child: isSameLine
          ? Row(
              crossAxisAlignment: rowCrossAxisAlignment ?? CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: _listBuild(),
            )
          : Column(
              crossAxisAlignment: rowCrossAxisAlignment ?? CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: _listBuild(),
            ),
    );
  }
}

规划Input组件的编写

在vue里边咱们是能够给input组件传递一个双向绑定的值,可是这件事在flutter中似乎欠好完成,所以这儿咱们能够通过TextEditingController的方法绕一下完成双向绑定计划。

import 'package:flutter/material.dart';
import 'package:ventora/styles/colors_lando.dart';
class InputText<T> extends StatelessWidget {
  final Function(String)? setValue;
  final String? hintText;
  final TextInputType? keyboardType;
  final TextEditingController controller;
  final double? textHeight;
  final EdgeInsetsGeometry? contentPadding;
  final bool? enabled;
  const InputText(
      {super.key, required this.setValue, this.hintText, this.keyboardType, required this.controller, this.textHeight, this.contentPadding, this.enabled});
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: textHeight ?? 15.0 * 1.5,
      width: double.infinity,
      child: TextField(
          enabled: enabled ?? true,
          style: TextStyle(color: enabled != false ? ColorsLandon.initColors : ColorsLandon.formHintColor, fontSize: 15, fontWeight: FontWeight.w600),
          cursorColor: ColorsLandon.initColors,
          cursorHeight: textHeight ?? 15.0 * 1.5,
          decoration: InputDecoration(
            border: InputBorder.none,
            contentPadding: contentPadding ?? const EdgeInsets.only(bottom: 10),
            hintText: hintText, 
            hintStyle: const TextStyle(color: ColorsLandon.hintInitColors, fontSize: 15),
          ),
          controller: controller,
          onChanged: setValue,
          keyboardType: keyboardType ?? TextInputType.text),
    );
  }
}

规划select组件

首要给予移动端的交互逻辑咱们能够参考一下小程序或者vant的ui逻辑能够规划成点击出现一个抽屉让用户挑选。

用element-plus的思想去封装一个flutter的表单方案
已然规划定下来了咱们能够写一下完成

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:ventora/styles/colors_lando.dart';
import 'package:ventora/styles/text_style_lando.dart';
class Select extends StatelessWidget {
  final String value;
  final Function setValue;
  final String? hintText;
  final List<String> selectList;
  final TextEditingController _controller = TextEditingController();
  Select({super.key, required this.value, required this.setValue, this.hintText, required this.selectList});
  @override
  Widget build(BuildContext context) {
    _controller.text = value;
    return GestureDetector(
        onTap: () => {
              showModalBottomSheet(
                  context: context,
                  builder: (context) => Container(
                        color: ColorsLandon.backgroundColor,
                        width: double.infinity,
                        padding: const EdgeInsets.only(bottom: 38, top: 20, left: 20, right: 20),
                        child: Column(mainAxisSize: MainAxisSize.min, children: [
                          ...selectList
                              .map((item) => GestureDetector(
                                  onTap: () {
                                    setValue(item);
                                    Navigator.pop(context);
                                  },
                                  child: Container(
                                      width: double.infinity,
                                      margin: const EdgeInsets.only(bottom: 16),
                                      decoration: const BoxDecoration(color: ColorsLandon.hintBackground, borderRadius: BorderRadius.all(Radius.circular(12))),
                                      padding: const EdgeInsets.all(16),
                                      child: Row(
                                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                                        children: [
                                          Text(
                                            item,
                                            style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600),
                                          ),
                                          if (item == value) const Icon(Icons.done, color: ColorsLandon.initColors)
                                        ],
                                      ))))
                              .toList(),
                          const SizedBox(
                            height: 20,
                          )
                        ]),
                      ))
            },
        child: Container(
            height: 18.0,
            alignment: Alignment.centerRight,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  value,
                  style: TextStyleLando.formItemText,
                ),
                const Icon(
                  Icons.expand_more,
                  color: Colors.white,
                  size: 16,
                )
              ],
            )));
  }
}

成果

其实整个form可贵当地在于规划思路不同的组件能够用flutter自带的组件来完成即可,主要是咱们需求先规划formItem怎么怎么规划会对使用者更加友爱,使用作用

 FormItem(
   type: FormType.text,
   label: 'Name',
   controller: logic.iForm.nickname,
   hintText: '4~30 characters',
 FormItem(
   type: FormType.select,
   label: 'Gender',
   setValue: (value) => {
     logic.setValue(logic.iForm.gender, value),
   },
   value: logic.iForm.gender.value,
   selectList: const [' Male', ' Female', '⚧ Non-binary'],
 )),

用element-plus的思想去封装一个flutter的表单方案

竣工下机!