大家好,我又來了。 今年這個冬天真的是“寒冬”啊,我是真的被“凍傷”了,一年的計劃全部被打算了,賊無奈,也讓我遭受了一定的打擊,希望之光在哪?(吐槽到此為止) 回到我們們的正題,剛用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();
},
);
}
}
複製程式碼
大功告成,這個時候我們就實現了想要的效果啦。 回想了下我寫的部落格,基本都是程式碼偏多,我把該有的說明都在程式碼中寫成註釋了,我覺得這樣更加的直觀,希望各位喜歡這種方式,如果本文幫助到了你,希望你能點點喜歡,給我一點點鼓勵,每次看到有人評論和點了喜歡,都會很開心,哈哈。要是能點點關注就更好了。
有啥問題歡迎及時聯絡我,我們下次再見啦!
程式碼來啦Github傳送門 喜歡的話,麻煩點點star哦!