Flutter仿微信,支付宝密码输入框+自定义键盘

7,483 阅读7分钟

版权声明:本文为博主原创文章,转载请注明出处!

刚用Flutter做完一个金融项目,当中使用到了类似于微信,和支付宝的那种密码输入框,然后为了安全一点也自己实现了自定义的键盘,今天跟大家分享一波

效果展示

效果如下图所示:

Flutter自定义密码——键盘.jpg

当中的布局形式,大家可根据自己的具体需求来调整就好了,我这里写的demo是这样的布局,这个调整起来很简单(本来想弄成gif的,然而不会。。。)。

分析一波

我们分析下这个东东,首先我们需要自定义好这个密码输入框,当我们在输入一个密码的时候,密码输入框就填充一位 ,这个过程其实我们自己把它绘制出来就好:

  1. 先绘制六个密码框
  2. 接受调用者传过来的密码,根据密码长度来绘制密码框的填充个数

输入框实现

///  自定义 密码输入框 第一步 —— 使用画笔画出单个的框
class CustomJPasswordField extends StatelessWidget {

  ///  传入当前密码
 String data;
  CustomJPasswordField(this.data);

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: MyCustom(data),
    );
  }
}

  ///  继承CustomPainter ,来实现自定义图形绘制
class MyCustom extends CustomPainter {

  ///  传入的密码,通过其长度来绘制圆点
  String pwdLength;
  MyCustom(this.pwdLength);

   ///  此处Sizes是指使用该类的父布局大小
  @override
  void paint(Canvas canvas, Size size) {

    // 密码画笔
  Paint mPwdPaint;
    Paint mRectPaint;

    // 初始化密码画笔  
    mPwdPaint = new Paint();
    mPwdPaint..color = Colors.black;

//   mPwdPaint.setAntiAlias(true);
    // 初始化密码框  
    mRectPaint = new Paint();
    mRectPaint..color = Color(0xff707070);

   ///  圆角矩形的绘制
    RRect r = new RRect.fromLTRBR(
        0.0, 0.0, size.width, size.height, new Radius.circular(size.height / 12));
   ///  画笔的风格
    mRectPaint.style = PaintingStyle.stroke;
    canvas.drawRRect(r, mRectPaint);

   ///  将其分成六个 格子(六位支付密码)
    var per = size.width / 6.0;
    var offsetX = per;
    while (offsetX < size.width) {
      canvas.drawLine(
          new Offset(offsetX, 0.0), new Offset(offsetX, size.height), mRectPaint);
      offsetX += per;
    }
 
    ///  画实心圆
    var half = per/2;
    var radio = per/8;
    mPwdPaint.style = PaintingStyle.fill;
    ///  当前有几位密码,画几个实心圆
    for(int i =0; i< pwdLength.length && i< 6; i++){
      canvas.drawArc(new Rect.fromLTRB(i*per+half-radio, size.height/2-radio, i*per+half+radio, size.height/2+radio), 0.0, 2*pi, true, mPwdPaint);
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

自定义键盘

到这里为止,我们就写完了我们第一个重头,自定义的密码输入框,然后第二步,实现自定义密码键盘,密码键盘也可以通过完全自定义绘制出来,但是我这里用的一种比较简单的实现方式,直接使用多个按钮组装成一个键盘,

自定义键盘.png

这个键盘其实就是12个相同样式的按钮组成,只是各自的文字内容不同,因此我们首先可以定义好一个公共的按钮样式,然后我们在其中通过回调的方式来将点击事件抛给调用者定义,

import 'package:flutter/material.dart';

///  自定义 键盘 按钮
class CustomKbBtn extends StatefulWidget {
///  按钮显示的文本内容
  String text;

  CustomKbBtn({Key key, this.text, this.callback}) : super(key: key);
 ///  按钮 点击事件的回调函数
  final callback;
  @override
  State<StatefulWidget> createState() {
    return ButtonState();
  }
}

class ButtonState extends State<CustomKbBtn> {
  ///回调函数执行体
  var backMethod;

  void back() {
    widget.callback('$backMethod');
  }

  @override
  Widget build(BuildContext context) {

 /// 获取当前屏幕的总宽度,从而得出单个按钮的宽度
    MediaQueryData mediaQuery = MediaQuery.of(context);
    var _screenWidth = mediaQuery.size.width;

    return new Container(
        height:50.0,
        width: _screenWidth / 3,
        child: new OutlineButton(
          // 直角
          shape: new RoundedRectangleBorder(
              borderRadius: new BorderRadius.circular(0.0)),
          // 边框颜色
          borderSide: new BorderSide(color: Color(0x10333333)),
          child: new Text(
            widget.text,
            style: new TextStyle(color: Color(0xff333333), fontSize: 20.0),
          ),
         // 按钮点击事件
          onPressed: back,
        ));
  }
}

自定义键盘代码实现

有了按钮之后,我们就将它拼装成一个完整的键盘:


/// 自定义密码 键盘

class MyKeyboard extends StatefulWidget {
  final callback;

  MyKeyboard(this.callback);

  @override
  State<StatefulWidget> createState() {
    return new MyKeyboardStat();
  }
}

class MyKeyboardStat extends State<MyKeyboard> {
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  /// 定义 确定 按钮 接口  暴露给调用方
  ///回调函数执行体
  var backMethod;
  void onCommitChange() {
    widget.callback(new KeyEvent("commit"));
  }

  void onOneChange(BuildContext cont) {
    widget.callback(new KeyEvent("1"));
  }

  void onTwoChange(BuildContext cont) {
    widget.callback(new KeyEvent("2"));
  }

  void onThreeChange(BuildContext cont) {
    widget.callback(new KeyEvent("3"));
  }

  void onFourChange(BuildContext cont) {
    widget.callback(new KeyEvent("4"));
  }

  void onFiveChange(BuildContext cont) {
    widget.callback(new KeyEvent("5"));
  }

  void onSixChange(BuildContext cont) {
    widget.callback(new KeyEvent("6"));
  }

  void onSevenChange(BuildContext cont) {
    widget.callback(new KeyEvent("7"));
  }

  void onEightChange(BuildContext cont) {
    widget.callback(new KeyEvent("8"));
  }

  void onNineChange(BuildContext cont) {
    widget.callback(new KeyEvent("9"));
  }

  void onZeroChange(BuildContext cont) {
    widget.callback(new KeyEvent("0"));
  }

  /// 点击删除
  void onDeleteChange() {
    widget.callback(new KeyEvent("del"));
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      key: _scaffoldKey,
      width: double.infinity,
      height: 250.0,
      color: Colors.white,
      child: new Column(
        children: <Widget>[
          new Container(
            height:30.0,
            color: Colors.white,
            alignment: Alignment.center,
            child: new Text(
              '下滑隐藏',
              style: new TextStyle(fontSize: 12.0, color: Color(0xff999999)),
            ),
          ),

          ///  键盘主体
          new Column(
            children: <Widget>[
              ///  第一行
              new Row(
                children: <Widget>[
                  CustomKbBtn(
                      text: '1', callback: (val) => onOneChange(context)),
                  CustomKbBtn(
                      text: '2', callback: (val) => onTwoChange(context)),
                  CustomKbBtn(
                      text: '3', callback: (val) => onThreeChange(context)),
                ],
              ),

              ///  第二行
              new Row(
                children: <Widget>[
                  CustomKbBtn(
                      text: '4', callback: (val) => onFourChange(context)),
                  CustomKbBtn(
                      text: '5', callback: (val) => onFiveChange(context)),
                  CustomKbBtn(
                      text: '6', callback: (val) => onSixChange(context)),
                ],
              ),

              ///  第三行
              new Row(
                children: <Widget>[
                  CustomKbBtn(
                      text: '7', callback: (val) => onSevenChange(context)),
                  CustomKbBtn(
                      text: '8', callback: (val) => onEightChange(context)),
                  CustomKbBtn(
                      text: '9', callback: (val) => onNineChange(context)),
                ],
              ),

              ///  第四行
              new Row(
                children: <Widget>[
                  CustomKbBtn(text: '删除', callback: (val) => onDeleteChange()),
                  CustomKbBtn(
                      text: '0', callback: (val) => onZeroChange(context)),
                  CustomKbBtn(text: '确定', callback: (val) => onCommitChange()),
                ],
              ),
            ],
          )
        ],
      ),
    );
  }
}

这里的回调函数,其实是将所有的按钮事件处理交给调用者自己去处理, 这里就引出了代码中的KeyEvent()这个类,我们看看这个类的实现

///  支符密码  用于 密码输入框和键盘之间进行通信
class KeyEvent {
 ///  当前点击的按钮所代表的值
  String key;
  KeyEvent(this.key);

  bool isDelete() => this.key == "del";
  bool isCommit() => this.key == "commit";
}

这个类实际上只是拿到了按钮最终代表的实际内容,然后调用者可以根据这个key的值来判断当前点击的是 数字按钮 还是说是 删除按钮 或者是 确定按钮,以此来进行密码的修改,。

到这里为止,所有的内容基本都准备好了,接下来就是使用了: 这里得注意一个点,密码键盘是从屏幕的最下方弹出来的,这里我使用到了Flutter的showBottomSheet,这个是一个官方的widget,通过这个来实现键盘的弹出。

直接上代码吧

/// 支付密码  +  自定义键盘

class main_keyboard extends StatefulWidget {
  static final String sName = "enter";

  @override
  State<StatefulWidget> createState() {
    return new keyboardState();
  }
}


class keyboardState extends State<main_keyboard> {
 /// 用户输入的密码
  String pwdData = '';

 /*
    GlobalKey:整个应用程序中唯一的键
    ScaffoldState:Scaffold框架的状态
    解释:_scaffoldKey的值是Scaffold框架状态的唯一键
   */
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  // VoidCallback:没有参数并且不返回数据的回调
  VoidCallback _showBottomSheetCallback;

  @override
  void initState() {

    _showBottomSheetCallback = _showBottomSheet;
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      key: _scaffoldKey,
      body: _buildContent(context),
    );
  }

  Widget _buildContent(BuildContext c) {
    return new Container(
      width: double.maxFinite,
      height: 300.0,
      color: Color(0xffffffff),
      child: new Column(
        children: <Widget>[

          new Padding(
            padding: const EdgeInsets.only(top: 50.0),
            child: new Text(
              '请在此输入新支付密码',
              style: new TextStyle(fontSize: 18.0, color: Color(0xff333333)),
            ),
          ),

          ///密码框
          new Padding(
            padding: const EdgeInsets.only(top: 15.0),
            child: _buildPwd(pwdData),
          ),
        ],
      ),
    );
  }

  /// 密码键盘 确认按钮 事件
  void onAffirmButton() {

  }

/// 密码键盘的整体回调,根据不同的按钮事件来进行相应的逻辑实现
  void _onKeyDown(KeyEvent data){
// 如果点击了删除按钮,则将密码进行修改
    if (data.isDelete()) {
      if (pwdData.length > 0) {
        pwdData = pwdData.substring(0, pwdData.length - 1);
        setState(() {});
      }
    } 
// 点击了确定按钮时
else if (data.isCommit()) {
      if (pwdData.length != 6) {
//        Fluttertoast.showToast(msg: "密码不足6位,请重试", gravity: ToastGravity.CENTER);
        return;
      }
      onAffirmButton();
    } 
//点击了数字按钮时  将密码进行完整的拼接
else {
      if (pwdData.length < 6) {
        pwdData += data.key;
      }
      setState(() {});
    }
  }
  /// 底部弹出 自定义键盘  下滑消失
  void _showBottomSheet() {
    setState(() {
      // disable the button  // 禁用按钮
      _showBottomSheetCallback = null;
    });

 /*
      currentState:获取具有此全局键的树中的控件状态
      showBottomSheet:显示持久性的质感设计底部面板
      解释:联系上文,_scaffoldKey是Scaffold框架状态的唯一键,因此代码大意为,
           在Scaffold框架中显示持久性的质感设计底部面板
     */
    _scaffoldKey.currentState
        .showBottomSheet<void>((BuildContext context) {
     /// 将自定义的密码键盘作为其child   这里将回调函数传入
      return new MyKeyboard(_onKeyDown);
    })
        .closed
        .whenComplete(() {
      if (mounted) {
        setState(() {
          // re-enable the button  // 重新启用按钮
          _showBottomSheetCallback = _showBottomSheet;
        });
      }
    });
  }

/// 构建 密码输入框  定义了其宽度和高度
  Widget _buildPwd(var pwd) {
    return new GestureDetector(
      child: new Container(
        width: 250.0,
        height:40.0,
//      color: Colors.white,  自定义密码输入框的使用
        child: new CustomJPasswordField(pwd),
      ),
// 用户点击输入框的时候,弹出自定义的键盘
      onTap: () {
        _showBottomSheetCallback();
      },
    );
  }
}

大功告成,这个时候我们就实现了想要的效果啦。 回想了下我写的博客,基本都是代码偏多,我把该有的说明都在代码中写成注释了,我觉得这样更加的直观,希望各位喜欢这种方式,如果本文帮助到了你,希望你能点点喜欢,给我一点点鼓励,每次看到有人评论和点了喜欢,都会很开心,哈哈。要是能点点关注就更好了。

有啥问题欢迎及时联系我,我们下次再见啦!

代码来啦Github传送门 喜欢的话,麻烦点点star哦!

欢迎大家关注公众号 : 面试者说 这里会分享面试过程中的方方面面,包括简历优化,面试准备,各类面经,经验分享,等等有意思的事情。