用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'],
 )),

完工下机!

相关推荐
你挚爱的强哥27 分钟前
【sgCreateCallAPIFunctionParam】自定义小工具:敏捷开发→调用接口方法参数生成工具
前端·javascript·vue.js
米老鼠的摩托车日记35 分钟前
【vue element-ui】关于删除按钮的提示框,可一键复制
前端·javascript·vue.js
前期后期1 小时前
Android OkHttp源码分析(一):为什么OkHttp的请求速度很快?为什么可以高扩展?为什么可以高并发
android·okhttp
猿饵块1 小时前
cmake--get_filename_component
java·前端·c++
大表哥61 小时前
在react中 使用redux
前端·react.js·前端框架
十月ooOO2 小时前
【解决】chrome 谷歌浏览器,鼠标点击任何区域都是 Input 输入框的状态,能看到输入的光标
前端·chrome·计算机外设
qq_339191142 小时前
spring boot admin集成,springboot2.x集成监控
java·前端·spring boot
pan_junbiao2 小时前
Vue使用代理方式解决跨域问题
前端·javascript·vue.js
明天…ling2 小时前
Web前端开发
前端·css·网络·前端框架·html·web
ROCKY_8172 小时前
web前端-HTML常用标签-综合案例
前端·html