版權(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)瓢棒,今天跟大家分享一波
效果如下圖所示:
當(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)就好:
- 先繪制六個(gè)密碼框
- 接受調(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)尾膊,
這個(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哦摧冀!