Flutter仿微信,支付寶密碼輸入框+自定義鍵盤

Yinll發表於2018-12-12

大家好,我又來了。 今年這個冬天真的是“寒冬”啊,我是真的被“凍傷”了,一年的計劃全部被打算了,賊無奈,也讓我遭受了一定的打擊,希望之光在哪?(吐槽到此為止) 回到我們們的正題,剛用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哦!

相關文章