效果展示
初始想法:本来想直接四个输入框,然后监听输入满四个字符,自动next焦点,但是粘贴显示的交互不太友好,加上四个textController + 四个FocusNode总感觉太冗余
最终想法:模拟一个输入框和光标,隐藏真正的输入框及禁用长按操作,自实现粘贴功能(粘贴会清除原来的字符串,完全替换成粘贴板的字符串)
全部代码:
/* *
* 输入框
* 允许十六位字母+数字格式
* 分开显示 每组四个字符
* */
class LongCode extends StatefulWidget {
final BuildContext ctx;
final TextEditingController textEditingController;
final FocusNode focusNode;
LongCode({@required this.ctx, this.textEditingController, this.focusNode});
@override
_LongCodeState createState() => _LongCodeState();
}
class _LongCodeState extends State<LongCode> with SingleTickerProviderStateMixin {
String code = '';
// 模拟光标动画
Animation<double> animation;
AnimationController controller;
bool isShowPaste = false;//是否展示粘贴按钮
@override
void initState() {
super.initState();
//光标动画
controller = AnimationController(duration: const Duration(seconds: 1), vsync: this);
animation = new Tween(begin: 1.0, end: 0.0).animate(controller);
controller.repeat();
// 监听输入框变化
// 不使用onchange是因为:clear()不会触发onchange,但监听可以监听到clear()
widget.textEditingController.addListener(() {
if (this.mounted) {
setState(() {
this.code = widget.textEditingController.text;
});
}
});
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
// 光标样式动画
_cursor() {
return AnimatedBuilder(
animation: animation,
builder: (BuildContext ctx, Widget child) {
return Container(
width: 30.w,
alignment: Alignment.center,
child: Container(
width: 2.w,
height: 34.w,
color: Color.fromRGBO(54, 54, 54, animation.value),
),
);
},
);
}
// 每个item样式
_getTextItem(String text, {hasCursor = false}) {
List<Widget> col = [];
for (var i = 0; i < text.length; i++) {
col.add(Container(width: 30.w, alignment: Alignment.center, child: Text(text[i])));
}
if (text.length < 4 && hasCursor) {
col.add(_cursor());
}
return Container(
width: 122.w,
height: 56.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.w),
border: Border.all(color: Color(0xFFCBCBCB), style: BorderStyle.solid, width: 1.w),
),
child: Row(
children: col,
),
);
}
// 总体样式 分成四个小框
_getTextRow() {
List<Widget> col = [];
bool hasCursor = false;
for (var i = 0; i < 4; i++) {
String text = this.code;
String subText = '';
hasCursor = false;
if (text.length > i * 4 + 4) {
subText = text.substring(i * 4, i * 4 + 4);
} else if (text.length > i * 4) {
subText = text.substring(i * 4);
}
if (text.length ~/ 4 == i) {
hasCursor = true;
}
col.add(_getTextItem(subText, hasCursor: hasCursor));
if (i != 3) {
col.add(Padding(
padding: EdgeInsets.symmetric(horizontal: 7.w),
child: Text(
'-',
style: TextStyle(
fontSize: 30.sp, height: 42 / 30, color: Color(0xFFCBCBCB), fontWeight: FontWeight.normal, decoration: TextDecoration.none),
),
));
}
}
return col;
}
// 隐形输入框
_getTextField() {
return Opacity(
opacity: 0,
child: TextField(
focusNode: widget.focusNode,
controller: widget.textEditingController,
decoration: new InputDecoration(
hintText: '',
isDense: true,
contentPadding: EdgeInsets.only(bottom: 0.w),
),
style: TextStyle(color: Colors.transparent),
cursorWidth: 0,
toolbarOptions: ToolbarOptions(),// 禁用长按toolbar功能
textInputAction: TextInputAction.done,
maxLines: 1,
minLines: 1,
inputFormatters: [
LengthLimitingTextInputFormatter(16), //长度限制
WhitelistingTextInputFormatter(RegExp("[a-zA-Z]|[0-9]")), //只能输入字母和数字
],
),
);
}
//自实现粘贴按钮
_getPasteBtn() {
return GestureDetector(
onTap: () {
setState(() {
this.isShowPaste = false;
});
Clipboard.getData('text/plain').then((value) {
setState(() {
this.isShowPaste = false;
widget.textEditingController.text = value.text;
//手动设置输入框光标位置到字符串尾部,否则删除键会报错
widget.textEditingController.selection = TextSelection.fromPosition(TextPosition(offset: value.text.length));
});
});
},
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10.w)),
color: Colors.white,
border: Border.all(color: Color(0xFFCBCBCB), style: BorderStyle.solid, width: 1.w),
boxShadow: [
BoxShadow(
color: Color.fromRGBO(216, 183, 183, 0.16),
offset: Offset(0.w, 10.w), //阴影xy轴偏移量
blurRadius: 26.w, //阴影模糊程度
),
],
),
width: 96.w,
height: 60.w,
child: Text(
'粘贴',
style: TextStyle(
fontSize: 24.sp, height: 36 / 24, color: Color(0xFF464646), fontWeight: FontWeight.normal, decoration: TextDecoration.none),
),
),
);
}
@override
Widget build(BuildContext context) {
this.code = widget.textEditingController.text;
return Stack(
overflow: Overflow.visible,
children: <Widget>[
GestureDetector(
onTap: () {
widget.focusNode.requestFocus();
},
onLongPress: () {
setState(() {
this.isShowPaste = true;
});
},
child: Column(
children: <Widget>[
//兑换码显示框
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _getTextRow(),
),
//隐形输入框
_getTextField(),
],
),
),
//粘贴按钮
Positioned(
top: 30.w,
left: 262.w,
child: Offstage(
offstage: !isShowPaste,
child: _getPasteBtn(),
),
),
],
);
}
}