123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- //import 'package:flutter_common_utils/lcfarm_size.dart';
- //import 'package:kappa_app/utils/lcfarm_color.dart';
- /// @desc 短信验证码输入框
- /// @time 2019-05-14 16:16
- /// @author Cheney
- class LcfarmCodeInput extends StatefulWidget {
- /// The max length of pin.
- final int codeLength;
- /// The callback will execute when user click done.
- final ValueChanged<String> onSubmit;
- /// Decorate the pin.
- final CodeDecoration decoration;
- /// Just like [TextField]'s inputFormatter.
- final List<TextInputFormatter>? inputFormatters;
- /// Just like [TextField]'s keyboardType.
- final TextInputType keyboardType;
- /// Same as [TextField]'s autoFocus.
- final bool autoFocus;
- /// Same as [TextField]'s focusNode.
- final FocusNode? focusNode;
- /// Same as [TextField]'s textInputAction.
- final TextInputAction textInputAction;
- LcfarmCodeInput({
- GlobalKey<LcfarmCodeInputState>? key,
- this.codeLength = 6,
- required this.onSubmit,
- this.decoration = const UnderlineDecoration(),
- List<TextInputFormatter>? inputFormatter,
- this.keyboardType = TextInputType.number,
- this.focusNode,
- this.autoFocus = false,
- this.textInputAction = TextInputAction.done,
- }) : inputFormatters = inputFormatter ??
- <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
- super(key: key);
- @override
- State createState() {
- return LcfarmCodeInputState();
- }
- }
- class LcfarmCodeInputState extends State<LcfarmCodeInput>
- with SingleTickerProviderStateMixin {
- ///输入监听器
- TextEditingController _controller = TextEditingController();
- /// The display text to the user.
- String? _text;
- AnimationController? _animationController;
- Animation<double>? _animation;
- FocusNode? _focusNode;
- @override
- void initState() {
- _focusNode = FocusNode();
- _controller.addListener(() {
- setState(() {
- _text = _controller.text;
- });
- submit(_controller.text);
- });
- _animationController =
- AnimationController(duration: Duration(milliseconds: 500), vsync: this);
- _animation = Tween(begin: 0.0, end: 255.0).animate(_animationController!)
- ..addStatusListener((status) {
- if (status == AnimationStatus.completed) {
- //动画执行结束时反向执行动画
- _animationController!.reverse();
- } else if (status == AnimationStatus.dismissed) {
- //动画恢复到初始状态时执行动画(正向)
- _animationController!.forward();
- }
- })
- ..addListener(() {
- setState(() {});
- });
- ///启动动画
- _animationController!.forward();
- super.initState();
- }
- void submit(String text) {
- if (text.length >= widget.codeLength) {
- widget.onSubmit(text.substring(0, widget.codeLength));
- _controller.text = "";
- //外部有传focusNode就直接使用外部的,没有则使用内部定义的
- widget.focusNode == null
- ? _focusNode?.unfocus()
- : widget.focusNode?.unfocus();
- }
- }
- @override
- void dispose() {
- /// Only execute when the controller is autoDispose.
- _controller.dispose();
- _animationController?.dispose();
- _focusNode?.dispose();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- return CustomPaint(
- /// The foreground paint to display pin.
- foregroundPainter: _CodePaint(
- text: _text ??"",
- codeLength: widget.codeLength,
- decoration: widget.decoration,
- alpha: _animation?.value.toInt() ?? 0,
- ),
- child: RepaintBoundary(
- child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
- TextField(
- /// Actual textEditingController.
- controller: _controller,
- /// Fake the text style.
- style: TextStyle(
- /// Hide the editing text.
- color: Colors.transparent,
- ),
- /// Hide the Cursor.
- cursorColor: Colors.transparent,
- /// Hide the cursor.
- cursorWidth: 0.0,
- /// No need to correct the user input.
- autocorrect: false,
- /// Center the input to make more natrual.
- textAlign: TextAlign.center,
- /// Disable the actual textField selection.
- enableInteractiveSelection: false,
- /// The maxLength of the pin input, the default value is 6.
- maxLength: widget.codeLength,
- /// If use system keyboard and user click done, it will execute callback
- /// Note!!! Custom keyboard in Android will not execute, see the related issue [https://github.com/flutter/flutter/issues/19027]
- onSubmitted: submit,
- /// Default text input type is number.
- keyboardType: widget.keyboardType,
- /// only accept digits.
- inputFormatters: widget.inputFormatters,
- /// Defines the keyboard focus for this widget.
- focusNode: widget.focusNode == null ? _focusNode : widget.focusNode,
- /// {@macro flutter.widgets.editableText.autofocus}
- autofocus: widget.autoFocus,
- /// The type of action button to use for the keyboard.
- ///
- /// Defaults to [TextInputAction.done]
- textInputAction: widget.textInputAction,
- /// {@macro flutter.widgets.editableText.obscureText}
- /// Default value of the obscureText is false. Make
- obscureText: true,
- /// Clear default text decoration.
- decoration: InputDecoration(
- /// Hide the counterText
- counterText: '',
- contentPadding: EdgeInsets.symmetric(vertical: 24.0),
- /// Hide the outline border.
- border: OutlineInputBorder(
- borderSide: BorderSide.none,
- ),
- ),
- ),
- ]),
- ),
- );
- }
- }
- class _CodePaint extends CustomPainter {
- String text;
- final int codeLength;
- final double space;
- final CodeDecoration? decoration;
- final int? alpha;
- _CodePaint({
- required this.text,
- required this.codeLength,
- this.decoration,
- this.space = 4.0,
- this.alpha = 0,
- }) {
- this.text = text.trim();
- }
- @override
- bool shouldRepaint(CustomPainter oldDelegate) =>
- !(oldDelegate is _CodePaint && oldDelegate.text == this.text);
- _drawUnderLine(Canvas canvas, Size size) {
- /// Force convert to [UnderlineDecoration].
- ///
- if(decoration == null)
- return;
- var dr = decoration as UnderlineDecoration;
- Paint underlinePaint = Paint()
- ..color = dr.color
- ..strokeWidth = dr.lineHeight
- ..style = PaintingStyle.stroke
- ..isAntiAlias = true;
- var startX = 0.0;
- var startY = size.height;
- /// 画下划线
- double singleWidth =
- (size.width - (codeLength - 1) * dr.gapSpace) / codeLength;
- for (int i = 0; i < codeLength; i++) {
- if (i == text.length && dr.enteredColor != null) {
- underlinePaint.color = dr.enteredColor;
- underlinePaint.strokeWidth = 1;
- } else {
- underlinePaint.color = dr.color;
- underlinePaint.strokeWidth = 0.5;
- }
- canvas.drawLine(Offset(startX, startY),
- Offset(startX + singleWidth, startY), underlinePaint);
- startX += singleWidth + dr.gapSpace;
- }
- /// 画文本
- var index = 0;
- startX = 0.0;
- startY = 28.0;
- /// Determine whether display obscureText.
- bool obscureOn;
- obscureOn = decoration!.obscureStyle != null &&
- decoration!.obscureStyle?.isTextObscure == true;
- /// The text style of pin.
- TextStyle textStyle;
- if (decoration!.textStyle == null) {
- textStyle = defaultStyle;
- } else {
- textStyle = decoration!.textStyle!;
- }
- text.runes.forEach((rune) {
- String code;
- if (obscureOn) {
- code = decoration!.obscureStyle?.obscureText??"";
- } else {
- code = String.fromCharCode(rune);
- }
- TextPainter textPainter = TextPainter(
- text: TextSpan(
- style: textStyle,
- text: code,
- ),
- textAlign: TextAlign.center,
- textDirection: TextDirection.ltr,
- );
- /// Layout the text.
- textPainter.layout();
- startX = singleWidth * index +
- singleWidth / 2 -
- textPainter.width / 2 +
- dr.gapSpace * index;
- textPainter.paint(canvas, Offset(startX, startY));
- index++;
- });
- ///画光标 如果外部有传,则直接使用外部
- Color cursorColor = dr.enteredColor != null
- ? dr.enteredColor
- : Color.fromRGBO(255, 145, 0, 1);
- cursorColor = cursorColor.withAlpha(alpha ?? 0);
- double cursorWidth = 1;
- double cursorHeight = 24;
- //LogUtil.v("animation.value=$alpha");
- Paint cursorPaint = Paint()
- ..color = cursorColor
- ..strokeWidth = cursorWidth
- ..style = PaintingStyle.stroke
- ..isAntiAlias = true;
- startX = text.length * (singleWidth + dr.gapSpace) + singleWidth / 2;
- var endX = startX + cursorWidth;
- var endY = startY + cursorHeight;
- // var endY = size.height - 28.0 - 12;
- // canvas.drawLine(Offset(startX, startY), Offset(startX, endY), cursorPaint);
- //绘制圆角光标
- Rect rect = Rect.fromLTRB(startX, startY, endX, endY);
- RRect rrect = RRect.fromRectAndRadius(rect, Radius.circular(cursorWidth));
- canvas.drawRRect(rrect, cursorPaint);
- }
- @override
- void paint(Canvas canvas, Size size) {
- _drawUnderLine(canvas, size);
- }
- }
- /// 默认的样式
- const TextStyle defaultStyle = TextStyle(
- /// Default text color.
- color: Colors.black,
- /// Default text size.
- fontSize: 24.0,
- );
- abstract class CodeDecoration {
- /// The style of painting text.
- final TextStyle? textStyle;
- final ObscureStyle? obscureStyle;
- const CodeDecoration({
- this.textStyle,
- this.obscureStyle,
- });
- }
- /// The object determine the obscure display
- class ObscureStyle {
- /// Determine whether replace [obscureText] with number.
- final bool isTextObscure;
- /// The display text when [isTextObscure] is true
- final String obscureText;
- const ObscureStyle({
- this.isTextObscure = false,
- this.obscureText = '*',
- }) : assert(obscureText.length == 1);
- }
- /// The object determine the underline color etc.
- class UnderlineDecoration extends CodeDecoration {
- /// The space between text and underline.
- final double gapSpace;
- /// The color of the underline.
- final Color color;
- /// The height of the underline.
- final double lineHeight;
- /// The underline changed color when user enter pin.
- final Color enteredColor;
- static const _enterColor = Color.fromRGBO(255, 145, 0, 1);
- const UnderlineDecoration({
- TextStyle? textStyle,
- ObscureStyle? obscureStyle,
- this.enteredColor = _enterColor,
- this.gapSpace = 15.0,
- this.color = Colors.black,
- this.lineHeight = 0.5,
- }) : super(
- textStyle: textStyle,
- obscureStyle: obscureStyle,
- );
- }
|