Flutter仿微信,支付寶密碼輸入框+自定義鍵盤(pán)

版權(quán)聲明:本文為博主原創(chuàng)文章甫菠,轉(zhuǎn)載請(qǐng)注明出處挠铲!

大家好,我又來(lái)了寂诱。
剛用Flutter做完一個(gè)金融項(xiàng)目拂苹,當(dāng)中使用到了類(lèi)似于微信,和支付寶的那種密碼輸入框痰洒,然后為了安全一點(diǎn)也自己實(shí)現(xiàn)了自定義的鍵盤(pán)瓢棒,今天跟大家分享一波
效果如下圖所示:


Flutter自定義密碼——鍵盤(pán).jpg

當(dāng)中的布局形式浴韭,大家可根據(jù)自己的具體需求來(lái)調(diào)整就好了,我這里寫(xiě)的demo是這樣的布局脯宿,這個(gè)調(diào)整起來(lái)很簡(jiǎn)單(本來(lái)想弄成gif的念颈,然而不會(huì)。连霉。榴芳。)。

我們分析下這個(gè)東東跺撼,首先我們需要自定義好這個(gè)密碼輸入框窟感,當(dāng)我們?cè)谳斎胍粋€(gè)密碼的時(shí)候,密碼輸入框就填充一位 歉井,這個(gè)過(guò)程其實(shí)我們自己把它繪制出來(lái)就好:

  1. 先繪制六個(gè)密碼框
  2. 接受調(diào)用者傳過(guò)來(lái)的密碼柿祈,根據(jù)密碼長(zhǎng)度來(lái)繪制密碼框的填充個(gè)數(shù)
///  自定義 密碼輸入框 第一步 —— 使用畫(huà)筆畫(huà)出單個(gè)的框
class CustomJPasswordField extends StatelessWidget {

  ///  傳入當(dāng)前密碼
 String data;
  CustomJPasswordField(this.data);

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

  ///  繼承CustomPainter ,來(lái)實(shí)現(xiàn)自定義圖形繪制
class MyCustom extends CustomPainter {

  ///  傳入的密碼酣难,通過(guò)其長(zhǎng)度來(lái)繪制圓點(diǎn)
  String pwdLength;
  MyCustom(this.pwdLength);

   ///  此處Sizes是指使用該類(lèi)的父布局大小
  @override
  void paint(Canvas canvas, Size size) {

    // 密碼畫(huà)筆
  Paint mPwdPaint;
    Paint mRectPaint;

    // 初始化密碼畫(huà)筆  
    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));
   ///  畫(huà)筆的風(fēng)格
    mRectPaint.style = PaintingStyle.stroke;
    canvas.drawRRect(r, mRectPaint);

   ///  將其分成六個(gè) 格子(六位支付密碼)
    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;
    }
 
    ///  畫(huà)實(shí)心圓
    var half = per/2;
    var radio = per/8;
    mPwdPaint.style = PaintingStyle.fill;
    ///  當(dāng)前有幾位密碼谍夭,畫(huà)幾個(gè)實(shí)心圓
    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;
  }
}

到這里為止,我們就寫(xiě)完了我們第一個(gè)重頭憨募,自定義的密碼輸入框紧索,然后第二步,實(shí)現(xiàn)自定義密碼鍵盤(pán)菜谣,密碼鍵盤(pán)也可以通過(guò)完全自定義繪制出來(lái)珠漂,但是我這里用的一種比較簡(jiǎn)單的實(shí)現(xiàn)方式,直接使用多個(gè)按鈕組裝成一個(gè)鍵盤(pán)尾膊,


自定義鍵盤(pán).png

這個(gè)鍵盤(pán)其實(shí)就是12個(gè)相同樣式的按鈕組成媳危,只是各自的文字內(nèi)容不同,因此我們首先可以定義好一個(gè)公共的按鈕樣式冈敛,然后我們?cè)谄渲型ㄟ^(guò)回調(diào)的方式來(lái)將點(diǎn)擊事件拋給調(diào)用者定義待笑,

import 'package:flutter/material.dart';

///  自定義 鍵盤(pán) 按鈕
class CustomKbBtn extends StatefulWidget {
///  按鈕顯示的文本內(nèi)容
  String text;

  CustomKbBtn({Key key, this.text, this.callback}) : super(key: key);
 ///  按鈕 點(diǎn)擊事件的回調(diào)函數(shù)
  final callback;
  @override
  State<StatefulWidget> createState() {
    return ButtonState();
  }
}

class ButtonState extends State<CustomKbBtn> {
  ///回調(diào)函數(shù)執(zhí)行體
  var backMethod;

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

  @override
  Widget build(BuildContext context) {

 /// 獲取當(dāng)前屏幕的總寬度,從而得出單個(gè)按鈕的寬度
    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),
          ),
         // 按鈕點(diǎn)擊事件
          onPressed: back,
        ));
  }
}

有了按鈕之后抓谴,我們就將它拼裝成一個(gè)完整的鍵盤(pán):


/// 自定義密碼 鍵盤(pán)

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>();

  /// 定義 確定 按鈕 接口  暴露給調(diào)用方
  ///回調(diào)函數(shù)執(zhí)行體
  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"));
  }

  /// 點(diǎn)擊刪除
  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)),
            ),
          ),

          ///  鍵盤(pán)主體
          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()),
                ],
              ),
            ],
          )
        ],
      ),
    );
  }
}

這里的回調(diào)函數(shù)暮蹂,其實(shí)是將所有的按鈕事件處理交給調(diào)用者自己去處理,
這里就引出了代碼中的KeyEvent()這個(gè)類(lèi)癌压,我們看看這個(gè)類(lèi)的實(shí)現(xiàn)

///  支符密碼  用于 密碼輸入框和鍵盤(pán)之間進(jìn)行通信
class KeyEvent {
 ///  當(dāng)前點(diǎn)擊的按鈕所代表的值
  String key;
  KeyEvent(this.key);

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

這個(gè)類(lèi)實(shí)際上只是拿到了按鈕最終代表的實(shí)際內(nèi)容仰泻,然后調(diào)用者可以根據(jù)這個(gè)key的值來(lái)判斷當(dāng)前點(diǎn)擊的是 數(shù)字按鈕 還是說(shuō)是 刪除按鈕 或者是 確定按鈕,以此來(lái)進(jìn)行密碼的修改滩届,集侯。

到這里為止,所有的內(nèi)容基本都準(zhǔn)備好了,接下來(lái)就是使用了:
這里得注意一個(gè)點(diǎn)棠枉,密碼鍵盤(pán)是從屏幕的最下方彈出來(lái)的浓体,這里我使用到了Flutter的showBottomSheet,這個(gè)是一個(gè)官方的widget,通過(guò)這個(gè)來(lái)實(shí)現(xiàn)鍵盤(pán)的彈出辈讶。

直接上代碼吧

/// 支付密碼  +  自定義鍵盤(pán)

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

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


class keyboardState extends State<main_keyboard> {
 /// 用戶(hù)輸入的密碼
  String pwdData = '';

 /*
    GlobalKey:整個(gè)應(yīng)用程序中唯一的鍵
    ScaffoldState:Scaffold框架的狀態(tài)
    解釋?zhuān)篲scaffoldKey的值是Scaffold框架狀態(tài)的唯一鍵
   */
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  // VoidCallback:沒(méi)有參數(shù)并且不返回?cái)?shù)據(jù)的回調(diào)
  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(
              '請(qǐng)?jiān)诖溯斎胄轮Ц睹艽a',
              style: new TextStyle(fontSize: 18.0, color: Color(0xff333333)),
            ),
          ),

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

  /// 密碼鍵盤(pán) 確認(rèn)按鈕 事件
  void onAffirmButton() {

  }

/// 密碼鍵盤(pán)的整體回調(diào)汹碱,根據(jù)不同的按鈕事件來(lái)進(jìn)行相應(yīng)的邏輯實(shí)現(xiàn)
  void _onKeyDown(KeyEvent data){
// 如果點(diǎn)擊了刪除按鈕,則將密碼進(jìn)行修改
    if (data.isDelete()) {
      if (pwdData.length > 0) {
        pwdData = pwdData.substring(0, pwdData.length - 1);
        setState(() {});
      }
    } 
// 點(diǎn)擊了確定按鈕時(shí)
else if (data.isCommit()) {
      if (pwdData.length != 6) {
//        Fluttertoast.showToast(msg: "密碼不足6位荞估,請(qǐng)重試", gravity: ToastGravity.CENTER);
        return;
      }
      onAffirmButton();
    } 
//點(diǎn)擊了數(shù)字按鈕時(shí)  將密碼進(jìn)行完整的拼接
else {
      if (pwdData.length < 6) {
        pwdData += data.key;
      }
      setState(() {});
    }
  }
  /// 底部彈出 自定義鍵盤(pán)  下滑消失
  void _showBottomSheet() {
    setState(() {
      // disable the button  // 禁用按鈕
      _showBottomSheetCallback = null;
    });

 /*
      currentState:獲取具有此全局鍵的樹(shù)中的控件狀態(tài)
      showBottomSheet:顯示持久性的質(zhì)感設(shè)計(jì)底部面板
      解釋?zhuān)郝?lián)系上文咳促,_scaffoldKey是Scaffold框架狀態(tài)的唯一鍵,因此代碼大意為勘伺,
           在Scaffold框架中顯示持久性的質(zhì)感設(shè)計(jì)底部面板
     */
    _scaffoldKey.currentState
        .showBottomSheet<void>((BuildContext context) {
     /// 將自定義的密碼鍵盤(pán)作為其child   這里將回調(diào)函數(shù)傳入
      return new MyKeyboard(_onKeyDown);
    })
        .closed
        .whenComplete(() {
      if (mounted) {
        setState(() {
          // re-enable the button  // 重新啟用按鈕
          _showBottomSheetCallback = _showBottomSheet;
        });
      }
    });
  }

/// 構(gòu)建 密碼輸入框  定義了其寬度和高度
  Widget _buildPwd(var pwd) {
    return new GestureDetector(
      child: new Container(
        width: 250.0,
        height:40.0,
//      color: Colors.white,  自定義密碼輸入框的使用
        child: new CustomJPasswordField(pwd),
      ),
// 用戶(hù)點(diǎn)擊輸入框的時(shí)候跪腹,彈出自定義的鍵盤(pán)
      onTap: () {
        _showBottomSheetCallback();
      },
    );
  }
}

大功告成,這個(gè)時(shí)候我們就實(shí)現(xiàn)了想要的效果啦飞醉。
回想了下我寫(xiě)的博客冲茸,基本都是代碼偏多,我把該有的說(shuō)明都在代碼中寫(xiě)成注釋了缅帘,我覺(jué)得這樣更加的直觀轴术,希望各位喜歡這種方式,如果本文幫助到了你钦无,希望你能點(diǎn)點(diǎn)喜歡逗栽,給我一點(diǎn)點(diǎn)鼓勵(lì),每次看到有人評(píng)論和點(diǎn)了喜歡失暂,都會(huì)很開(kāi)心彼宠,哈哈。要是能點(diǎn)點(diǎn)關(guān)注就更好了弟塞。

有啥問(wèn)題歡迎及時(shí)聯(lián)系我凭峡,我們下次再見(jiàn)啦!

代碼來(lái)啦Github傳送門(mén)
喜歡的話(huà)决记,麻煩點(diǎn)點(diǎn)star哦摧冀!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市系宫,隨后出現(xiàn)的幾起案子索昂,更是在濱河造成了極大的恐慌,老刑警劉巖笙瑟,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件楼镐,死亡現(xiàn)場(chǎng)離奇詭異癞志,居然都是意外死亡往枷,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)错洁,“玉大人秉宿,你說(shuō)我怎么就攤上這事⊥筒辏” “怎么了描睦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)导而。 經(jīng)常有香客問(wèn)我忱叭,道長(zhǎng),這世上最難降的妖魔是什么今艺? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任韵丑,我火速辦了婚禮,結(jié)果婚禮上虚缎,老公的妹妹穿的比我還像新娘撵彻。我一直安慰自己,他們只是感情好实牡,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布陌僵。 她就那樣靜靜地躺著,像睡著了一般创坞。 火紅的嫁衣襯著肌膚如雪碗短。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天题涨,我揣著相機(jī)與錄音豪椿,去河邊找鬼。 笑死携栋,一個(gè)胖子當(dāng)著我的面吹牛搭盾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播婉支,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼鸯隅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了向挖?” 一聲冷哼從身側(cè)響起蝌以,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎何之,沒(méi)想到半個(gè)月后跟畅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溶推,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年徊件,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奸攻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡虱痕,死狀恐怖睹耐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情部翘,我是刑警寧澤硝训,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站新思,受9級(jí)特大地震影響窖梁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夹囚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一窄绒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧崔兴,春花似錦彰导、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至堰燎,卻和暖如春掏父,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背秆剪。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工赊淑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人仅讽。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓陶缺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親洁灵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子饱岸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容