v8
This commit is contained in:
175
lib/go_router/examples/input/decoration/code_decoration.dart
Normal file
175
lib/go_router/examples/input/decoration/code_decoration.dart
Normal file
@@ -0,0 +1,175 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class CodeDecoration {
|
||||
final Color activeColor;
|
||||
final Color inactiveColor;
|
||||
final Size cursorSize;
|
||||
final Color cursorColor;
|
||||
|
||||
final TextStyle textStyle;
|
||||
final String? obscureText;
|
||||
|
||||
CodeDecoration({
|
||||
required this.activeColor,
|
||||
required this.inactiveColor,
|
||||
required this.textStyle,
|
||||
required this.cursorSize,
|
||||
required this.cursorColor,
|
||||
this.obscureText,
|
||||
});
|
||||
|
||||
void paint(
|
||||
Canvas canvas, Size size, int alpha, String text, int count, double gap) {
|
||||
double boxWidth = (size.width - (count - 1) * gap) / count;
|
||||
|
||||
/// 绘制装饰
|
||||
paintDecoration(canvas, size, text, count, gap);
|
||||
|
||||
/// 绘制游标
|
||||
paintCursor(canvas, alpha,size, text.length, boxWidth, gap);
|
||||
|
||||
/// 绘制文字
|
||||
paintText(canvas,size, text, boxWidth, gap);
|
||||
}
|
||||
|
||||
void paintCursor(
|
||||
Canvas canvas, int alpha,Size size, int count, double boxWidth, double gap) {
|
||||
Paint cursorPaint = Paint()
|
||||
..color = cursorColor.withAlpha(alpha)
|
||||
..strokeWidth = cursorSize.width
|
||||
..style = PaintingStyle.fill
|
||||
..isAntiAlias = true;
|
||||
|
||||
double startX = count * (boxWidth + gap) + boxWidth / 2;
|
||||
double startY = size.height/2-cursorSize.height/2;
|
||||
var endX = startX + cursorSize.width;
|
||||
var endY = size.height/2 + cursorSize.height/2;
|
||||
// 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(cursorSize.width));
|
||||
canvas.drawRRect(rrect, cursorPaint);
|
||||
}
|
||||
|
||||
void paintText(Canvas canvas, Size size,String text, double boxWidth, double gap) {
|
||||
/// 画文本
|
||||
double startX = 0.0;
|
||||
/// Determine whether display obscureText.
|
||||
bool obscure = obscureText != null;
|
||||
|
||||
for (int i = 0; i < text.runes.length; i++) {
|
||||
int rune = text.runes.elementAt(i);
|
||||
String code = obscure ? obscureText! : String.fromCharCode(rune);
|
||||
TextPainter textPainter = TextPainter(
|
||||
text: TextSpan(
|
||||
style: textStyle,
|
||||
text: code,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
|
||||
/// Layout the text.
|
||||
textPainter.layout();
|
||||
var startY = size.height/2-textPainter.height/2;
|
||||
startX = boxWidth * i + boxWidth / 2 - textPainter.width / 2 + gap * i;
|
||||
textPainter.paint(canvas, Offset(startX, startY));
|
||||
}
|
||||
}
|
||||
|
||||
void paintDecoration(
|
||||
Canvas canvas, Size size, String text, int count, double gap);
|
||||
}
|
||||
|
||||
class UnderlineCodeDecoration extends CodeDecoration {
|
||||
final double strokeWidth;
|
||||
|
||||
UnderlineCodeDecoration(
|
||||
{required this.strokeWidth,
|
||||
required super.activeColor,
|
||||
required super.inactiveColor,
|
||||
required super.cursorColor,
|
||||
required super.textStyle,
|
||||
required super.cursorSize,
|
||||
super.obscureText});
|
||||
|
||||
@override
|
||||
void paintDecoration(
|
||||
Canvas canvas, Size size, String text, int count, double gap) {
|
||||
Paint underlinePaint = Paint()
|
||||
..color = activeColor
|
||||
..strokeWidth = strokeWidth
|
||||
..style = PaintingStyle.stroke
|
||||
..isAntiAlias = true;
|
||||
|
||||
/// 画下划线
|
||||
double singleWidth = (size.width - (count - 1) * gap) / count;
|
||||
var startX = 0.0;
|
||||
var startY = size.height;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (i == text.length) {
|
||||
underlinePaint.color = activeColor;
|
||||
// underlinePaint.strokeWidth = strokeWidth;
|
||||
} else {
|
||||
underlinePaint.color = inactiveColor;
|
||||
// underlinePaint.strokeWidth = strokeWidth;
|
||||
}
|
||||
canvas.drawLine(Offset(startX, startY),
|
||||
Offset(startX + singleWidth, startY), underlinePaint);
|
||||
startX += singleWidth + gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RRectCodeDecoration extends CodeDecoration {
|
||||
final double strokeWidth;
|
||||
final double height;
|
||||
|
||||
RRectCodeDecoration(
|
||||
{required this.height,
|
||||
required this.strokeWidth,
|
||||
required super.activeColor,
|
||||
required super.inactiveColor,
|
||||
required super.cursorColor,
|
||||
required super.textStyle,
|
||||
required super.cursorSize,
|
||||
super.obscureText});
|
||||
|
||||
@override
|
||||
void paintDecoration(
|
||||
Canvas canvas, Size size, String text, int count, double gap) {
|
||||
Paint paint = Paint()
|
||||
..color = activeColor
|
||||
..strokeWidth = strokeWidth
|
||||
..style = PaintingStyle.stroke
|
||||
..isAntiAlias = true;
|
||||
|
||||
/// 画下划线
|
||||
double singleWidth = (size.width - (count - 1) * gap) / count;
|
||||
var startX = 0.0;
|
||||
var startY = size.height;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (i == text.length) {
|
||||
paint.color = activeColor;
|
||||
} else {
|
||||
paint.color = inactiveColor;
|
||||
}
|
||||
RRect rRect = RRect.fromRectAndRadius(
|
||||
Rect.fromPoints(
|
||||
Offset(startX, 0),
|
||||
Offset(startX + singleWidth, startY),
|
||||
),
|
||||
Radius.circular(6));
|
||||
canvas.drawRRect(rRect, paint);
|
||||
// canvas.drawLine(Offset(startX, startY),
|
||||
// Offset(startX + singleWidth, startY), underlinePaint);
|
||||
startX += singleWidth + gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
215
lib/go_router/examples/input/input.dart
Normal file
215
lib/go_router/examples/input/input.dart
Normal file
@@ -0,0 +1,215 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'decoration/code_decoration.dart';
|
||||
import 'input_painter.dart';
|
||||
|
||||
typedef AsyncSubmit = Future<bool> Function(String value);
|
||||
|
||||
/// @desc 短信验证码输入框
|
||||
/// @time 2019-05-14 16:16
|
||||
/// @author Cheney
|
||||
class TolyCodeInput extends StatefulWidget {
|
||||
final int count;
|
||||
|
||||
final AsyncSubmit onSubmit;
|
||||
|
||||
final CodeDecoration decoration;
|
||||
|
||||
final TextInputType keyboardType;
|
||||
|
||||
final bool autoFocus;
|
||||
|
||||
final FocusNode? focusNode;
|
||||
|
||||
final TextInputAction textInputAction;
|
||||
|
||||
const TolyCodeInput(
|
||||
{this.count = 6,
|
||||
required this.onSubmit,
|
||||
this.decoration = const UnderlineDecoration(),
|
||||
this.keyboardType = TextInputType.number,
|
||||
this.focusNode,
|
||||
this.autoFocus = false,
|
||||
this.textInputAction = TextInputAction.done,
|
||||
super.key});
|
||||
|
||||
@override
|
||||
State createState() {
|
||||
return TolyCodeInputState();
|
||||
}
|
||||
}
|
||||
|
||||
class TolyCodeInputState extends State<TolyCodeInput>
|
||||
with SingleTickerProviderStateMixin {
|
||||
///输入监听器
|
||||
final TextEditingController _controller = TextEditingController();
|
||||
late AnimationController _animaCtrl;
|
||||
late Animation<int> _animation;
|
||||
|
||||
late FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_focusNode = widget.focusNode ?? FocusNode();
|
||||
_controller.addListener(_listenValueChange);
|
||||
initAnimation();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void initAnimation() {
|
||||
_animaCtrl = AnimationController(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
vsync: this,
|
||||
);
|
||||
_animation = IntTween(begin: 0, end: 255).animate(_animaCtrl)
|
||||
..addStatusListener(_stateChange);
|
||||
_animaCtrl.forward();
|
||||
}
|
||||
|
||||
void _listenValueChange() {
|
||||
submit(_controller.text);
|
||||
}
|
||||
|
||||
void submit(String text) async{
|
||||
if (text.length >= widget.count) {
|
||||
bool success = await widget.onSubmit.call(text.substring(0, widget.count));
|
||||
if(success){
|
||||
_focusNode.unfocus();
|
||||
}else{
|
||||
_controller.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
/// Only execute when the controller is autoDispose.
|
||||
_controller.dispose();
|
||||
_animaCtrl.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomPaint(
|
||||
foregroundPainter: InputPainter(
|
||||
RRectCodeDecoration(
|
||||
height: 56,
|
||||
strokeWidth: 1,
|
||||
activeColor: const Color(0xff010101),
|
||||
inactiveColor: const Color(0xffB6B6B6),
|
||||
cursorColor: Color(0xff3776E9),
|
||||
textStyle: TextStyle(color: Colors.black),
|
||||
cursorSize: Size(1, 24),
|
||||
),
|
||||
controller: _controller,
|
||||
count: widget.count,
|
||||
decoration: widget.decoration,
|
||||
alpha: _animation,
|
||||
),
|
||||
child: RepaintBoundary(
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
/// Fake the text style.
|
||||
style: const TextStyle(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
cursorColor: Colors.transparent,
|
||||
cursorWidth: 0.0,
|
||||
autocorrect: false,
|
||||
textAlign: TextAlign.center,
|
||||
enableInteractiveSelection: false,
|
||||
maxLength: widget.count,
|
||||
onSubmitted: submit,
|
||||
keyboardType: widget.keyboardType,
|
||||
focusNode: _focusNode,
|
||||
autofocus: widget.autoFocus,
|
||||
textInputAction: widget.textInputAction,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
counterText: '',
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 24),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _stateChange(AnimationStatus status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
_animaCtrl.reverse();
|
||||
} else if (status == AnimationStatus.dismissed) {
|
||||
_animaCtrl.forward();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// 默认的样式
|
||||
const TextStyle defaultStyle = TextStyle(
|
||||
/// Default text color.
|
||||
color: Color(0x80000000),
|
||||
|
||||
/// 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;
|
||||
|
||||
const UnderlineDecoration({
|
||||
TextStyle? textStyle,
|
||||
ObscureStyle? obscureStyle,
|
||||
this.enteredColor = const Color(0xff3776E9),
|
||||
this.gapSpace = 15.0,
|
||||
this.color = const Color(0x24000000),
|
||||
this.lineHeight = 0.5,
|
||||
}) : super(
|
||||
textStyle: textStyle,
|
||||
obscureStyle: obscureStyle,
|
||||
);
|
||||
}
|
||||
141
lib/go_router/examples/input/input_painter.dart
Normal file
141
lib/go_router/examples/input/input_painter.dart
Normal file
@@ -0,0 +1,141 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import 'decoration/code_decoration.dart' as c;
|
||||
import 'input.dart';
|
||||
|
||||
class InputPainter extends CustomPainter {
|
||||
final TextEditingController controller;
|
||||
final int count;
|
||||
final double space;
|
||||
final CodeDecoration decoration;
|
||||
final c.CodeDecoration underlineCodeDecoration;
|
||||
final Animation<int> alpha;
|
||||
|
||||
InputPainter(
|
||||
this.underlineCodeDecoration, {
|
||||
required this.controller,
|
||||
required this.count,
|
||||
required this.decoration,
|
||||
this.space = 4.0,
|
||||
required this.alpha,
|
||||
}) : super(repaint: Listenable.merge([controller, alpha]));
|
||||
|
||||
void _drawUnderLine(Canvas canvas, Size size) {
|
||||
/// Force convert to [UnderlineDecoration].
|
||||
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 - (count - 1) * dr.gapSpace) / count;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (i == controller.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;
|
||||
|
||||
/// Determine whether display obscureText.
|
||||
bool obscureOn;
|
||||
obscureOn = decoration.obscureStyle != null &&
|
||||
decoration.obscureStyle!.isTextObscure;
|
||||
|
||||
/// The text style of pin.
|
||||
TextStyle textStyle;
|
||||
if (decoration.textStyle == null) {
|
||||
textStyle = defaultStyle;
|
||||
} else {
|
||||
textStyle = decoration.textStyle!;
|
||||
}
|
||||
|
||||
controller.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 ?? const Color(0xff3776E9);
|
||||
cursorColor = cursorColor.withAlpha(alpha.value);
|
||||
|
||||
double cursorWidth = 1;
|
||||
double cursorHeight = 24;
|
||||
|
||||
//LogUtil.v("animation.value=$alpha");
|
||||
|
||||
Paint cursorPaint = Paint()
|
||||
..color = cursorColor
|
||||
..strokeWidth = cursorWidth
|
||||
..style = PaintingStyle.stroke
|
||||
..isAntiAlias = true;
|
||||
|
||||
startX =
|
||||
controller.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);
|
||||
underlineCodeDecoration.paint(
|
||||
canvas,
|
||||
size,
|
||||
alpha.value,
|
||||
controller.text,
|
||||
count,
|
||||
space,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant InputPainter oldDelegate) {
|
||||
return oldDelegate.controller.text != controller.text;
|
||||
}
|
||||
}
|
||||
25
lib/go_router/examples/input/main.dart
Normal file
25
lib/go_router/examples/input/main.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'input.dart';
|
||||
|
||||
void main() => runApp(const MyApp());
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: TolyCodeInput(
|
||||
autoFocus: true,
|
||||
onSubmit: (value) async {
|
||||
print(value);
|
||||
return true;
|
||||
},
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user