Flutter 优雅实现密码强度校验结果

前言

我们经常在一些网站上看到这样的密码强度指示,使用三段线,分别用不同的颜色来表示弱密码、中等强度密码和强密码。这种方式能够让用户清晰地感知到自己设置密码的强度,体验上更为友好。本篇我们就用 Flutter 来实现这样一个密码强度校验示例。最终完成的效果如下图。

组件分析

这里面实际上是密码强度要动态随密码输入框的内容改变,因此需要监听输入框内容的变化。常规的做法是在输入框的 onChanged 方法里 setState 来更新整个组件。但是,实际上有更优雅的实现方式,那就是使用 Dart 的Stream 来实现局部刷新,也就是输入内容变化时只刷新密码强度指示区域。稍后我们会讲具体的实现。 界面上,实际上组件的层级关系如下图所示。

为了简化嵌套层级,我们将框起来的部分,也就是密码强度指示单独封装成一个组件,叫做PasswordStrengthIndicator。这个组件也就是需要跟随文本输入框内容变化而局部刷新的组件。这里顺带说一下,如果要考虑局部刷新,那么就需要合理的封装组件,将需要局部刷新的组件和不需要刷新的部分拆分开,这样就能够在最小范围控制要刷新的组件。 最后是密码强度校验的逻辑,我们的逻辑如下:

  • 密码长度低于8或者只包含数字、字母和特殊字符中的一个,则认为是弱密码;
  • 密码长度大于8,且只包含数字、字母和特殊字符中的两个,则认为是中等强度密码;
  • 密码长度大于8,且同时包含数字、字母和特殊字符,则认为是强密码。

这个逻辑可以通过正则匹配来完成。 最后是密码强度指示组件的实现,我们要保证三段线等宽且等比例,那么就可以用一个 Flex 组件包裹一个 Container 组件,设置比例 flex 值为1,就可以了。这样做法的好处是不需要写死 Container 的宽度,适用性更强。

代码实现

我们先来看一下 Stream 如何实现局部刷新。在 Flutter 里,提供了一个 基于 Stream 构建组件的 StreamBuilder类。当StreamBuilder监听到 Stream 内容发生变化时,就会调用其builder方法重新构建一个组件并返回。StreamBuilder的定义如下:

dart 复制代码
const StreamBuilder({
  super.key,
  this.initialData,
  super.stream,
  required this.builder,
});

这是一个泛型类,因此可以接收任何类型的数据,比如我们自定义的业务对象。其中各个参数说明如下:

  • initialData 是初始值;
  • stream即要监听的数据流,通常由一个 StreamController 提供(也可以是网络数据流)。
  • builder 即组件构建方法,该方法会携带context和数据流对象,我们可以基于数据流对象的数据来构建组件。

因此我们要监听密码输入框内容变化时,只需要使用一个StreamController对象,将变化后的内容添加到数据流中。这样,当输入框内容变化时,就会触发StreamBuilder刷新,我们从这个StreamController对象取出最新数据来构建界面即可。这部分对应的代码如下。

dart 复制代码
final StreamController<String> _inputController = StreamController<String>();
// ...

@override
Widget build(BuildContext context) {
  //...
  TextField(
    keyboardType: TextInputType.visiblePassword,
    onChanged: (text) {
      _inputController.add(text);
    },
    decoration: const InputDecoration(labelText: '请输入密码'),
  ),
  //...
  StreamBuilder<String>(
    stream: _inputController.stream,
    initialData: '',
    builder: (context, snapshot) {
      final passwordStrength = calculateStrength(snapshot.data!);
      return PasswordStrengthIndicator(
          passwordStrength: passwordStrength, lineHeight: 4.0);
    },
  ),
}

PasswordStrengthIndicator是自定义的密码指示器,实现的话是一个 Row 组件,通过 Flex 组件保持三段线(Container) 等宽,代码比较简单,如下所示。其中PasswordStrength是一个枚举,有 weakmediumstrong 三个值,分别对应弱密码、中等强度密码和强密码。

dart 复制代码
class PasswordStrengthIndicator extends StatelessWidget {
  final PasswordStrength passwordStrength;
  final double lineHeight;
  const PasswordStrengthIndicator(
      {super.key, required this.passwordStrength, required this.lineHeight});

  final _strengthGapWidth = 8.0;
  @override
  Widget build(BuildContext context) {
    var passwordIndicator =
        PasswordIndicator(passwordStrength: passwordStrength);
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Flexible(
          flex: 1,
          child: Container(
            height: lineHeight,
            color: passwordIndicator.lineColors[0],
          ),
        ),
        SizedBox(width: _strengthGapWidth),
        Flexible(
          flex: 1,
          child: Container(
            height: lineHeight,
            color: passwordIndicator.lineColors[1],
          ),
        ),
        SizedBox(width: _strengthGapWidth),
        Flexible(
          flex: 1,
          child: Container(
            height: lineHeight,
            color: passwordIndicator.lineColors[2],
          ),
        ),
        SizedBox(width: _strengthGapWidth),
        Text(
          passwordIndicator.strengthText,
          style: TextStyle(
            fontWeight: FontWeight.bold,
            color: passwordIndicator.strengthTextColor,
          ),
        ),
      ],
    );
  }
}

PasswordIndicator是一个自定义的实体类,主要是简化密码强度指示器的代码,将组件显示所需要的数据全部封装到这个实体类中,包括线段显示的颜色,密码强弱指示文字内容和文字颜色。代码如下:

dart 复制代码
class PasswordIndicator {
  final PasswordStrength passwordStrength;
  late List<Color> _lineColors;
  late String _strengthText;
  late Color _strengthTextColor;

  PasswordIndicator({required this.passwordStrength}) {
    switch (passwordStrength) {
      case PasswordStrength.weak:
        _lineColors = const [
          Color(0xFFCCCCCC),
          Color(0xFFCCCCCC),
          Color(0xFFCCCCCC)
        ];
        _strengthText = '弱';
        _strengthTextColor = Colors.red;
        break;
      case PasswordStrength.medium:
        _lineColors = const [
          Color(0xFF00A52B),
          Color(0xFF00A52B),
          Color(0xFFCCCCCC)
        ];
        _strengthText = '中等';
        _strengthTextColor = Colors.orange;
        break;
      default:
        _lineColors = const [
          Color(0xFF00A52B),
          Color(0xFF00A52B),
          Color(0xFF00A52B)
        ];
        _strengthText = '强';
        _strengthTextColor = const Color(0xFF00A52B);
        break;
    }
  }

  get lineColors => _lineColors;
  get strengthText => _strengthText;
  get strengthTextColor => _strengthTextColor;
}

最后是密码强度校验逻辑,这个使用方法calculateStrength实现,其实这个也可以作为一个工具方法使用。方法定义如下,主要是通过正则来判断密码的强弱。

dart 复制代码
PasswordStrength calculateStrength(String password) {
    if (password.length >= 8) {
      bool hasDigit = false;
      bool hasLetter = false;
      bool hasSpecial = false;

      for (var char in password.split('')) {
        if (RegExp(r'[0-9]').hasMatch(char)) {
          hasDigit = true;
        } else if (RegExp(r'[A-Za-z]').hasMatch(char)) {
          hasLetter = true;
        } else {
          hasSpecial = true;
        }
      }

      if (hasDigit && hasLetter && hasSpecial) {
        return PasswordStrength.strong;
      } else if ((hasDigit && hasLetter) ||
          (hasDigit && hasSpecial) ||
          (hasLetter && hasSpecial)) {
        return PasswordStrength.medium;
      }
    }
    return PasswordStrength.weak;
  }
}

完整代码

完整代码已经上传至gitee:Flutter实用组件源码

我是岛上码农,微信公众号同名。如有问题可以加本人微信交流,微信号:island-coder

👍🏻:觉得有收获请点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!

相关推荐
熊的猫1 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
瑶琴AI前端1 小时前
uniapp组件实现省市区三级联动选择
java·前端·uni-app
会发光的猪。1 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
我要洋人死2 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人3 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人3 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR3 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香3 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596933 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai3 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书