这个表单打死我也不填!

8,119 阅读9分钟

本文为掘金社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

前言

表单在应用内随处可见,注册、登录、完善个人资料、发表内容……在 B 端应用中,表单操作更是员工日常工作中使用最多的功能。好的表单体验能够让用户更加轻松地完成信息录入,从而让我们获知更多用户的信息、在应用内产生更多内容或者让员工的工作更为轻松。糟糕的表单,则会让用户感到绝望,有一种打死也不想填的感觉!那么,对于开发而言,如何提高表单操作的用户体验?本篇我们就来讲讲常见的提高表单用户体验的方式,推荐在封装表单组件的时候将这些因素考虑进去,形成公司内统一的规范,保持体验的一致性。

错误提示

表单输入不可避免会出错,及时、准确地给出错误提示能够让用户快速更正错误,提高表单录入的效率。错误提示有以下三种方式:

  • 内联提示:当输入完成后,立即校验表单内容并给出错误提示;
  • 提交时客户端提示:输入完成后,点击提交按钮时对整个表单进行错误校验和提示;
  • 提交后服务端校验:提交到后端后,有服务端应用对表单进行校验。

对于输入表单内容较少的错误提示,推荐采用内联的方式实现。内联方式的错误提示就是将错误信息直接显示在表单附近(通常是下方),这样用户更容易察觉。我们来看看在 Flutter 中如何实现内联的错误提示。 Flutter 的TextField 组件有一个 decoration属性,类型为InputDecoration。通过 InputDecoration 类的errorTexterrorStyle可以构建一个 Text 组件,当 errorText 不为空时就会默认在 TextField 下方显示错误指示,从而提示用户输入有误,示例代码和运行结果如下。需要注意的是,错误提示信息应该准确,避免泛泛的错误提示。我曾经遇到过的反面例子就是产品没有明确错误提示(很多产品因为见过不少标准的错误提示,就默认开发也知道如何提示错误,所以文档并不会给出错误提示文案),然后开发直接粗暴地显示一个“输入有误”的标准提示。

TextField(
  autofocus: true,
  decoration: InputDecoration(
    label: const Text('邮箱'),
    hintText: '请输入电子邮箱地址',
    errorText: _errorText,
    errorStyle: TextStyle(color: Colors.red[400], fontSize: 14.0),
  ),
  onEditingComplete: () {
    if (!FormUtils.isValidEmail(_email)) {
      setState(() {
        _errorText = '请输入正确的邮箱地址';
      });
    } else {
      setState(() {
        _errorText = null;
      });
    }
  },
  onChanged: (text) {
    _email = text;
  },
)

表单校验错误提示.gif 对于表单录入内容较多的情况来说,推荐使用提交时进行错误校验和提示。因为表单内容较多时,通常用户更希望专注输入内容,如果经常给出错误提示会转移用户注意力,打断用户填写表单的思路。

标签

表单的标签有三种,顶部标签、左侧标签和浮动标签,当然简洁的设计有时候会使用图标替代标签文字。研究表明,顶部标签的方式填写表单的效率更高,因为一路向下填写即可,视线不需要实现 Z 字形路线移动,但是顶部标签相对来说比较占空间,在移动端不太多见。大多数国外的应用顶部标签居多,例如下面是亚马逊的登录界面。 gmail 则别出新裁地在默认的时候省去了标签,一旦聚焦标签显示在输入框的上边框,可以说是将效率和空间做了充分的利用(这种效果在 Flutter 中默认就有,只要TextField 制定了边框,就会在聚焦后将标签显示在边框上)。 image.png

gmail.gif

在 Flutter 中,就如同我们上面的例子那样,默认标签是不可见的,聚焦后才会以小字的形式显示上面的标签。我们来看看 Flutter 文本输入框的标签具体如何设置。Flutter 为设置标签样式和交互提供了3个属性:

  • labelStyleTextStyle 对象,用于设置默认的标签样式。
  • floatingLabelStyle:设置浮动状态(即标签在输入框上方)时的标签状态,这个属性可以是 TextStyle 对象也可以是MaterialStateTextStyle对象,如果是MaterialStateTextStyle对象的话,就可以设置不同状态下的标签样式,比如错误、聚焦和光标移入等。
  • floatingLabelBehavior:设置浮动标签的交互行为,默认是auto,即聚焦的时候才显示,也可以设置为 never 不显示标签,或者设置为 always 一直显示标签。

下面是对应的代码,我们来看看不同形式的区别:

Column(
  children: [
    TextField(
      autofocus: true,
      decoration: InputDecoration(
        label: const Text('邮箱'),
        hintText: '请输入电子邮箱地址',
        floatingLabelStyle: MaterialStateTextStyle.resolveWith(
            (Set<MaterialState> states) {
          final Color color;
          if (states.contains(MaterialState.error)) {
            color = Colors.red[400]!;
          } else if (states.contains(MaterialState.focused)) {
            color = Colors.blue[400]!;
          } else {
            color = Colors.black54;
          }
          return TextStyle(
            color: color,
          );
        }),
        floatingLabelBehavior: FloatingLabelBehavior.always,
        errorText: _errorText,
        errorStyle: TextStyle(color: Colors.red[400], fontSize: 14.0),
      ),
      onEditingComplete: () {
        if (!FormUtils.isValidEmail(_email)) {
          setState(() {
            _errorText = '请输入正确的邮箱地址';
          });
        } else {
          setState(() {
            _errorText = null;
          });
        }
      },
      onChanged: (text) {
        _email = text;
      },
    ),
    TextField(
      decoration: InputDecoration(
        label: const Text('密码'),
        hintText: '请输入密码',
        floatingLabelBehavior: FloatingLabelBehavior.auto,
      ),
      obscureText: true,
    ),
    TextField(
      decoration: InputDecoration(
        label: const Text('确认密码'),
        hintText: '请再次输入密码',
        floatingLabelBehavior: FloatingLabelBehavior.never,
      ),
      obscureText: true,
    ),
  ],
),
  • 第一个表单邮箱我们使用了固定显示标签的形式,同时通过floatingLabelStyle设置了不同状态的标签颜色;
  • 第二个表单密码我们使用了auto 模式,可以看到标签一开始是在表单行内的,聚焦后标签移到了输入框上方,而输入框会展示占位文字;
  • 第三个确认密码我们使用了never 模式,标签一开始是在表单行内的,聚焦后标签移到了输入框上方,而输入框会展示占位文字,但标签会消失不见。

标签交互.gif 从体验上来说,第一种和第二种会更好些,不过在国内大部分是直接将标签省略或者使用左侧标签。下面是阿里云的注册界面,没有标签的表单适用于表单项较少的场景。 image.png 对于 Flutter 的 TextField,如果要使用左侧标签,则需要自定义实现(可以参考之前的文章:)。这里需要注意,虽然 TextField 提供了一个 prefix 可以自定义前置组件,但是这个组件只会在文本框聚焦的时候才显示。

键盘

键盘处理在手机端非常常见了,建议根据输入类型来设置键盘类型,常见的键盘类型和用途如下:

  • TextInputType.number:输入数字,带小数点;
  • TextInputType.phone:输入数字,*和#字符,适合拨打电话或输入号码;
  • TextInputType.email:输入电子邮箱地址;
  • TextInputType.url:适合输入链接地址,会默认显示/和.字符;
  • TextInputType.multiline:适合多行输入,带有回车符,回车后自动换行;

需要注意的是,安卓和 iOS 的键盘会有些区别,具体可以看看 TextInputType 的各个枚举值的说明。

表单项过多

当要录入的表单项很多的时候,如果表单没有条理性,会让用户望而生畏,忍受力强的用户发发闹骚,无法忍受的用户直接放弃。比如下面这样的注册表单,看到就会绝望,打死我也不会填的image.png image.png 怎么处理表单项过多?推荐的做法有三种:

  • 表单分组:比如将必填的表单分为一组,其他选填的表单分为一组;这种在 PC 端会更适用。
  • 分步骤填写:还是表单分组,但是分多个步骤,每页展示的表单数量少,不会让用户一下子感觉要填很多东西。而且,从心理学上来说,前面填了一部分会让用户积累沉没成本,后面的表单会更愿意填。同时,对于非必填的分组提供跳过选项。
  • 利用时间差:也就是一开始的时候让用户只填很少的信息,然后在之后的使用过程中逐步引导用户完善其他资料。比如在领英里,每次就会让我们填写一点点信息,最终将个人信息收集完整。

表单校验

前面错误提示的时候,已经讲过一些校验的内容了。这里要说的是,我们开发一定要养成对表单进行校验的习惯。本人就遇到过这样的情况,产品没有特别说明校验规则,然后前后端都不校验,结果录入的数据导致各种各样的 bug。这种写 bug 的行为会极大降低我们程序员的段位!对于校验,建议是在内部形成统一的默认规则,比如下面几点:

  • 文本输入的最大输入长度限制;
  • 数值的最大输入范围;
  • 小数默认保留的位数;
  • 常用类型数据的校验规则统一,例如姓名、邮箱、手机号、身份证号、经纬度、日期、企业统一社会信用代码,密码强度、金额等。避免前后端校验规则不一致。

至于什么时候校验,建议是按表单数量来定,表单数量少可以在输入完成后进行校验;表单数量多推荐是在完成输入后整体校验。同时,前端务必保证提交给后端数据的基本合法性(除了需要后端通过业务逻辑校验的除外,例如唯一性检查,是否存在检查等)。

总结

如果评估一个前端开发做事情的细致程度,通常会通过他写的表单业务来评估。基本上,测试一遍表单的输入交互、错误验证就能够判断出来。一个好的前端,即便是产品不说,也会按照规范将交互和基本的校验做好。同时,保持与产品的沟通也很重要,如果对表单的校验规则有疑惑,那一定是需要二次确认的。虽然不确认锅可以甩给产品,但是 bug 却是留在自己头上的。