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

大家好,我又来了。
刚用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传送门



作者:Yinll
链接:https://www.jianshu.com/p/675bff7a0d4a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以使用 `GestureDetector` 组件来监听长按事件,并在长按时显示一个底部的菜单栏,用于选择多个消息。下面是一个简单的示例代码: ```dart class ChatMessage { final String content; bool selected; ChatMessage(this.content, {this.selected = false}); } class ChatScreen extends StatefulWidget { @override _ChatScreenState createState() => _ChatScreenState(); } class _ChatScreenState extends State<ChatScreen> { final List<ChatMessage> messages = [ ChatMessage("Hello!"), ChatMessage("How are you?"), ChatMessage("I'm fine, thanks."), ChatMessage("What about you?"), ChatMessage("I'm good."), ]; List<ChatMessage> selectedMessages = []; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Chat"), ), body: ListView.builder( itemCount: messages.length, itemBuilder: (context, index) { final message = messages[index]; return GestureDetector( onLongPress: () { setState(() { message.selected = true; selectedMessages.add(message); }); }, child: Container( padding: EdgeInsets.all(16.0), color: message.selected ? Colors.grey[300] : null, child: Text(message.content), ), ); }, ), bottomNavigationBar: selectedMessages.isNotEmpty ? BottomAppBar( child: Row( children: [ IconButton( icon: Icon(Icons.delete), onPressed: () { setState(() { messages.removeWhere((message) => selectedMessages.contains(message)); selectedMessages.clear(); }); }, ), Text("${selectedMessages.length} selected"), ], ), ) : null, ); } } ``` 在这个示例中,我们创建了一个 `ChatMessage` 类来表示聊天消息。`selected` 属性表示该消息是否被选中。在 `ChatScreen` 中,我们创建了一个 `messages` 列表来存储所有的消息,以及一个 `selectedMessages` 列表来存储被选中的消息。在 `ListView.builder` 中,我们使用 `GestureDetector` 来监听长按事件,并在长按时将消息标记为选中状态,并将其添加到 `selectedMessages` 列表中。在底部菜单栏中,我们显示了一个删除按钮和一个文本框,用于显示选中的消息数量。当用户点击删除按钮时,我们从 `messages` 列表中删除所有被选中的消息,并清空 `selectedMessages` 列表。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值