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

起因

公司需要开发app项目,但是没有多余的flutter工程师,只能让我们的前端组去做flutter,基于java的开发经验快速的上手dart,但是一些开发思路还是偏前端,所以写了一套偏向于前端的flutter表单方案。

设计思路

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

承载组件的FormItem的设计

首先这里我们是需要支持用户传递他需要的字段的即label,然后对于布局方式我们也需要支持,在移动端有两种常见的布局方式对于表单项。

第二步item要支持对不同类型组件的渲染,这里我们可以用枚举来解决,比如我们支持inupt,select这两种类型。

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

datr 复制代码
// 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的方式绕一下实现双向绑定方案。

dart 复制代码
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逻辑可以设计成点击出现一个抽屉让用户选择。

既然设计定下来了我们可以写一下实现

dart 复制代码
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如何如何设计会对使用者更加友好,使用效果

dart 复制代码
 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'],
 )),

完工下机!

相关推荐
2401_8827275741 分钟前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
NoneCoder44 分钟前
CSS系列(36)-- Containment详解
前端·css
anyup_前端梦工厂1 小时前
初始 ShellJS:一个 Node.js 命令行工具集合
前端·javascript·node.js
5hand1 小时前
Element-ui的使用教程 基于HBuilder X
前端·javascript·vue.js·elementui
GDAL1 小时前
vue3入门教程:ref能否完全替代reactive?
前端·javascript·vue.js
六卿1 小时前
react防止页面崩溃
前端·react.js·前端框架
z千鑫2 小时前
【前端】详解前端三大主流框架:React、Vue与Angular的比较与选择
前端·vue.js·react.js
zhangphil2 小时前
Android绘图Path基于LinearGradient线性动画渐变,Kotlin(2)
android·kotlin
m0_748256142 小时前
前端 MYTED单篇TED词汇学习功能优化
前端·学习
watl03 小时前
【Android】unzip aar删除冲突classes再zip
android·linux·运维