Flutter仿微信,支付宝密码输入框+自定义键盘
大家好,我又来了。
今年这个冬天真的是“寒冬”啊,我是真的被“冻伤”了,一年的计划全部被打算了,贼无奈,也让我遭受了一定的打击,希望之光在哪?(吐槽到此为止)
回到咱们的正题,刚用Flutter做完一个金融项目,当中使用到了类似于微信,和支付宝的那种密码输入框,然后为了安全一点也自己实现了自定义的键盘,今天跟大家分享一波
效果如下图所示:
Flutter自定义密码——键盘.jpg
当中的布局形式,大家可根据自己的具体需求来调整就好了,我这里写的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;
}
}
到这里为止,我们就写完了我们第一个重头,自定义的密码输入框,然后第二步,实现自定义密码键盘,密码键盘也可以通过完全自定义绘制出来,但是我这里用的一种比较简单的实现方式,直接使用多个按钮组装成一个键盘,
自定义键盘.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哦!