Flutter仿微信,支付寶密碼輸入框+自定義鍵盤
大家好,我又來了。 今年這個冬天真的是“寒冬”啊,我是真的被“凍傷”了,一年的計劃全部被打算了,賊無奈,也讓我遭受了一定的打擊,希望之光在哪?(吐槽到此為止) 回到咱們的正題,剛用Flutter做完一個金融專案,當中使用到了類似於微信,和支付寶的那種密碼輸入框,然後為了安全一點也自己實現了自定義的鍵盤,今天跟大家分享一波
效果展示
效果如下圖所示:

當中的佈局形式,大家可根據自己的具體需求來調整就好了,我這裡寫的demo是這樣的佈局,這個調整起來很簡單(本來想弄成gif的,然而不會。。。)。
分析一波
我們分析下這個東東,首先我們需要自定義好這個密碼輸入框,當我們在輸入一個密碼的時候,密碼輸入框就填充一位 ,這個過程其實我們自己把它繪製出來就好:
- 先繪製六個密碼框
- 接受呼叫者傳過來的密碼,根據密碼長度來繪製密碼框的填充個數
輸入框實現
///自定義 密碼輸入框 第一步 —— 使用畫筆畫出單個的框 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; } } 複製程式碼
自定義鍵盤
到這裡為止,我們就寫完了我們第一個重頭,自定義的密碼輸入框,然後第二步,實現自定義密碼鍵盤,密碼鍵盤也可以通過完全自定義繪製出來,但是我這裡用的一種比較簡單的實現方式,直接使用多個按鈕組裝成一個鍵盤,

這個鍵盤其實就是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(); }, ); } } 複製程式碼
大功告成,這個時候我們就實現了想要的效果啦。 回想了下我寫的部落格,基本都是程式碼偏多,我把該有的說明都在程式碼中寫成註釋了,我覺得這樣更加的直觀,希望各位喜歡這種方式,如果本文幫助到了你,希望你能點點喜歡,給我一點點鼓勵,每次看到有人評論和點了喜歡,都會很開心,哈哈。要是能點點關注就更好了。
有啥問題歡迎及時聯絡我,我們下次再見啦!