学习flutter代码 实现一个用于输入验证码的自定义组件,它允许用户输入固定长度的验证码,并在输入完成时触发回调。
前置知识点学习
TextStyle
`TextStyle` 是 Flutter 中用于定义文本样式的类。它提供了一组属性来控制文本的外观,如字体大小、颜色、粗细、字间距等。`TextStyle` 是 Flutter 应用中处理文本样式的核心部分,通过它可以实现丰富的文本展示效果。
`TextStyle` 的主要属性
1.`color`: 设置文本的颜色。可以使用 `Colors` 类提供的颜色常量或自定义颜色。
2.`fontSize`: 设置文本的字号大小,以逻辑像素为单位。
3.`fontWeight`: 定义文本的粗细程度。可以使用 `FontWeight` 枚举,如 `FontWeight.bold`。
4.`fontStyle`: 设置文本的样式(正常或斜体)。可以使用 `FontStyle` 枚举,如 `FontStyle.italic`。
5.`letterSpacing`: 设置字母之间的间距。
6.`wordSpacing`: 设置单词之间的间距。
7.`fontFamily`: 指定文本所使用的字体系列。可以是系统字体,也可以是自定义字体。
8.`backgroundColor`: 文本的背景颜色。
9.`decoration`: 为文本添加装饰,如下划线、上划线或删除线。可以使用 `TextDecoration` 枚举。
10.`decorationColor`: 文本装饰的颜色。
11.`decorationStyle`: 定义装饰线的样式,如实线、虚线等。可以使用 `TextDecorationStyle` 枚举。
12.`height`: 行高,相对于字体大小的倍数。
示例代码
以下是一个使用 `TextStyle` 的示例,展示了如何应用各种样式属性:
import 'package:flutter/material.dart';
class TextStyleExample extends StatelessWidget {
const TextStyleExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TextStyle Example'),
),
body: const Center(
child: Text(
'Hello, Flutter!',
style: TextStyle(
color: Colors.blue,
fontSize: 24.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
letterSpacing: 2.0,
wordSpacing: 4.0,
fontFamily: 'Roboto',
decoration: TextDecoration.underline,
decorationColor: Colors.red,
decorationStyle: TextDecorationStyle.dashed,
),
),
),
);
}
}
代码解析
- 颜色与大小:`color: Colors.blue` 和 `fontSize: 24.0` 分别设置了文本的颜色和字号。
- 字体粗细与样式:使用 `fontWeight: FontWeight.bold` 使文本加粗,`fontStyle: FontStyle.italic` 设置为斜体。
- 字间距与单词间距:`letterSpacing: 2.0` 增加字母间距,`wordSpacing: 4.0` 增加单词间距。
- 字体系列:`fontFamily: 'Roboto'` 指定文本使用 Roboto 字体系列。
- 文本装饰:`decoration: TextDecoration.underline` 添加下划线,`decorationColor: Colors.red` 和 `decorationStyle: TextDecorationStyle.dashed` 分别设置装饰线的颜色和样式。
- 字体加载:当使用自定义字体时,确保在 `pubspec.yaml` 文件中正确配置字体资源,并在项目中包含字体文件。这将确保在所有平台上正确加载和显示自定义字体。
flutter:
fonts:
- family: Roboto
fonts:
- asset: fonts/Roboto-Regular.ttf
- asset: fonts/Roboto-Bold.ttf
weight: 700
- 性能考虑:在大量文本或频繁更新文本样式的情况下,尽量减少不必要的样式变化,以提高性能。使用相同的 `TextStyle` 实例可以减少构建时间。
- 继承与覆盖:`TextStyle` 支持通过 `copyWith` 方法创建一个新的样式对象,可以在现有样式的基础上进行局部修改:
TextStyle baseStyle = TextStyle(fontSize: 20.0, color: Colors.black);
TextStyle newStyle = baseStyle.copyWith(color: Colors.red);
这在需要在不同部分稍微调整样式时非常有用。
- 样式优先级:如果 `TextStyle` 被应用于 `Text` 小部件,而 `Text` 小部件又嵌套在一个使用 `DefaultTextStyle` 的父小部件中,那么 `Text` 小部件的 `TextStyle` 将覆盖 `DefaultTextStyle` 的样式。
实际应用中的建议
- 使用主题:在大型应用中,使用 `ThemeData` 来统一管理文本样式,可以通过 `Theme.of(context).textTheme` 获取预定义的文本样式。这样可以保持应用风格的一致性。
Text(
'Themed Text',
style: Theme.of(context).textTheme.headline6,
);
- 响应式设计:在处理需要适配不同屏幕尺寸的文本时,可以结合 `MediaQuery` 或使用 `flutter_screenutil` 这样的库,根据屏幕尺寸动态调整 `fontSize`。
- 国际化与本地化:在处理多语言支持时,确保字体和样式可以适应不同的字符集和语言特性。例如,某些语言可能需要更大的行高或不同的字体。
通过对 `TextStyle` 的深入理解,你可以在 Flutter 应用中创建美观、统一且响应式的文本展示效果。它是 Flutter UI 构建中不可或缺的一部分,熟练掌握它将帮助你更好地设计和实现高质量的用户界面。
ClipRRect
`ClipRRect` 是 Flutter 中的一个小部件,用于将其子组件裁剪为圆角矩形的形状。它非常适合在需要为组件添加圆角效果时使用,比如为图片、容器等添加圆角。`ClipRRect` 通过使用 `RRect`(圆角矩形)来定义裁剪的形状。
主要属性
1.`borderRadius`:
- 类型:`BorderRadius`
- 用于定义圆角的半径,可以指定每个角的圆角大小。
- 示例:`BorderRadius.circular(8.0)` 会为所有角设置 8.0 的圆角。
2.`clipBehavior`:
- 类型:`Clip`
- 定义裁剪行为。常用值包括 `Clip.none`、`Clip.hardEdge`、`Clip.antiAlias` 和 `Clip.antiAliasWithSaveLayer`。
- `Clip.antiAlias` 和 `Clip.antiAliasWithSaveLayer` 提供更平滑的边缘,但可能会影响性能。
3.`child`:
- 被裁剪的子组件。可以是任何类型的 Widget。
使用场景
- 应用圆角效果:为图片、按钮、容器等应用圆角效果。
- 嵌套裁剪:在需要复杂形状裁剪的场合,可以结合其他裁剪小部件(如 `ClipOval`)使用。
示例代码
下面是一个简单的 `ClipRRect` 使用示例,将一张图片裁剪为圆角矩形:
import 'package:flutter/material.dart';
class ClipRRectExample extends StatelessWidget {
const ClipRRectExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ClipRRect Example'),
),
body: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(16.0),
child: Image.asset(
"static/demo.png",
width: 200,
height: 200,
fit: BoxFit.cover,
),
),
),
);
}
}
代码解析
- `borderRadius`: 使用 `BorderRadius.circular(16.0)` 为图片的四个角应用了 16.0 的圆角。
- `child`: 包含了一张通过网络加载的图片。`ClipRRect` 将这张图片裁剪成带有圆角的矩形。
性能考虑
- 裁剪性能:使用 `ClipRRect` 时,`clipBehavior` 会影响性能,尤其是在需要抗锯齿或使用 `saveLayer` 时。在性能敏感的场景中,尽可能选择 `Clip.hardEdge`,因为它是最快的选项。
- 避免不必要的裁剪:如果可以通过布局或其他方式实现相同的效果,尽量避免使用 `ClipRRect`,因为裁剪操作会增加渲染开销。
总结
`ClipRRect` 是 Flutter 中用于实现圆角裁剪的强大工具。通过它,可以轻松地为任何组件添加圆角效果,使得应用的视觉效果更加美观和现代。在使用时,需要注意裁剪的性能影响,合理选择裁剪行为以达到最佳的性能和平滑度平衡。
AnimatedContainer
`AnimatedContainer` 是 Flutter 中一个非常有用的小部件,它在属性发生变化时可以自动动画化地过渡到新的属性。这使得创建动画效果和响应式界面变得非常简单,无需手动管理动画控制器或状态。
特性与属性
1.`duration`:
- 类型:`Duration`
- 指定动画过渡的持续时间。必须设置这个属性来定义动画的长短。
- 示例:`Duration(seconds: 1)` 表示动画持续一秒。
2.`curve`:
- 类型:`Curve`
- 定义动画的曲线,决定了动画的加速度和减速度。常用曲线有 `Curves.easeIn`、`Curves.easeOut`、`Curves.bounceIn` 等。
- 示例:`Curves.easeInOut` 表示动画以缓慢的速度开始和结束,但中间速度较快。
3.`alignment`:
- 控制子组件的对齐方式。
4.`padding`:
- 控制内部填充。
5.`color`:
- 背景颜色。
6.`decoration` 和 `foregroundDecoration`:
- `decoration` 用于背景装饰,例如边框、圆角、渐变等。
- `foregroundDecoration` 用于在子组件前的装饰。
7.`width` 和 `height`:
- 控制容器的宽度和高度。
8.`constraints`:
- 设置容器的大小限制,例如最小和最大宽高。
9.`margin`:
- 控制外部间距。
`10.transform`:
- 应用于子组件的变换,例如旋转、缩放、平移等。
11.`child`:
- 被包裹的子组件。
使用场景
- 动画化尺寸变化:在用户交互后,动态调整组件的大小。
- 背景颜色过渡:在状态改变时,平滑地过渡到新的背景颜色。
- 位置和边距动画:在布局中通过调整位置和边距来创建动画效果。
示例代码
import 'package:flutter/material.dart';
class AnimatedContainerExample extends StatefulWidget {
const AnimatedContainerExample({super.key});
@override
_AnimatedContainerExampleState createState() {
return _AnimatedContainerExampleState();
}
}
class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
double _width = 100.0;
double _height = 100.0;
Color _color = Colors.blue;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AnimatedContainer Example'),
),
body: Center(
child: GestureDetector(
onTap: () {
setState(() {
_width = _width == 100.0 ? 200.0 : 100.0;
_height = _height == 100.0 ? 200.0 : 100.0;
_color = _color == Colors.blue ? Colors.red : Colors.blue;
});
},
child: AnimatedContainer(
width: _width,
height: _height,
color: _color,
alignment: Alignment.center,
duration: const Duration(seconds: 1),
curve: Curves.easeInOut,
child: const Text(
'Tap me!',
style: TextStyle(color: Colors.white, fontSize: 20.0),
),
),
),
),
);
}
}
BoxDecoration
`BoxDecoration` 是 Flutter 中用于装饰容器(如 `Container`)的一个类。它提供了一系列属性,允许你为容器添加背景颜色、渐变、图像、边框、阴影等装饰效果。`BoxDecoration` 结合 `Container` 使用,可以打造出丰富的视觉效果。
主要属性
1.`color`:
- 类型:`Color`
- 设置容器的背景颜色。
2.`image`:
- 类型:`DecorationImage`
- 用于在容器背景中绘制图像。
- 可以指定图像的对齐方式、填充方式等。
3.`border`:
- 类型:`Border`
- 为容器添加边框。可以指定每条边的宽度和颜色。
4.`borderRadius`:
- 类型:`BorderRadius`
- 设置容器的圆角半径。
5.`boxShadow`:
- 类型:`List`
- 为容器添加阴影效果。可以指定阴影的颜色、偏移、模糊半径等。
6.`gradient`:
- 类型:`Gradient`
- 为容器添加渐变背景。支持线性渐变、径向渐变等。
7.`backgroundBlendMode`:
- 类型:`BlendMode`
- 设置背景颜色和图像的混合模式。
8.`shape`:
- 类型:`BoxShape`
- 指定容器的形状。可以是矩形(默认)或圆形。
使用场景
- 背景颜色和图像:为容器添加背景颜色或图像。
- 边框和圆角:为容器添加边框和圆角效果。
- 阴影效果:为容器添加阴影,提升视觉层次感。
- 渐变背景:使用渐变色为背景,打造现代化的视觉效果。
示例代码
以下是一个使用 `BoxDecoration` 的示例,展示了如何为容器添加背景颜色、圆角、边框和阴影:
import 'package:flutter/material.dart';
class BoxDecorationExample22 extends StatelessWidget {
const BoxDecorationExample22({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BoxDecoration Example'),
),
body: Container(
width: 200.0,
height: 200.0,
decoration: BoxDecoration(
color: Colors.blue[200],
borderRadius: BorderRadius.circular(20.0),
border: Border.all(
color: Colors.blue,
width: 3.0,
),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: const Offset(0, 3), // changes position of shadow
),
],
),
child: const Center(
child: Text(
'Hello, Flutter!',
style: TextStyle(color: Colors.white, fontSize: 20.0),
),
),
),
);
}
}
TextInputFormatter
`TextInputFormatter` 是 Flutter 中用于在用户输入文本时进行格式化和验证的一个抽象类。它允许你在文本被输入到文本字段之前对其进行修改或限制,这对于实现自定义的输入行为(如限制输入类型或长度)非常有用。
主要属性和方法
- `formatEditUpdate`:
- 方法:`TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue)`
- 这是 `TextInputFormatter` 的核心方法,必须被重写。它接收旧的文本值和新的文本值,返回一个格式化后的文本值。
- `TextEditingValue` 包含文本字段的当前状态,包括文本内容、光标位置和选择范围。
常用子类
1.`LengthLimitingTextInputFormatter`:
- 用于限制输入文本的最大长度。
- 构造时传入一个整数表示最大长度。
- 示例:`LengthLimitingTextInputFormatter(10)` 限制输入长度为 10 个字符。
2.`FilteringTextInputFormatter`:
- 用于过滤输入的文本,只允许符合特定模式的输入。
- 提供了 `FilteringTextInputFormatter.digitsOnly` 来限制输入仅为数字。
- 可以通过自定义正则表达式来实现更复杂的过滤。
示例代码
以下是如何使用 `LengthLimitingTextInputFormatter` 和 `FilteringTextInputFormatter` 的示例:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class TextInputFormatterExampleDemo extends StatelessWidget {
const TextInputFormatterExampleDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TextInputFormatter Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
decoration: const InputDecoration(
labelText: 'Limited Length (Max 10)',
),
inputFormatters: [
LengthLimitingTextInputFormatter(10),
],
),
const SizedBox(
height: 20,
),
TextField(
decoration: const InputDecoration(
labelText: 'Digits Only',
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
)
],
),
),
);
}
}
代码解析
- 限制输入长度:第一个文本字段使用 `LengthLimitingTextInputFormatter(10)` 限制输入最大长度为 10 个字符。
- 限制输入内容:第二个文本字段使用 `FilteringTextInputFormatter.digitsOnly` 限制输入仅为数字。
自定义格式化器
你可以通过继承 `TextInputFormatter` 并重写 `formatEditUpdate` 方法来创建自定义的输入格式化器。例如,创建一个格式化器来限制输入只能是大写字母:
class UpperCaseTextInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
return TextEditingValue(
text: newValue.text.toUpperCase(),
selection: newValue.selection,
);
}
}
使用场景
- 限制输入的类型和格式(例如,只允许字母、数字或特定符号)。
- 验证用户输入的格式是否符合特定规则(如电子邮件、电话号码)。
- 动态调整用户输入,使其符合特定格式。
TextField
`TextField` 是 Flutter 中用于获取用户文本输入的一个重要组件。它提供了强大的功能和高度的可定制性,使开发者能够轻松地集成和管理用户输入。下面是对 `TextField` 的详细解析。
基本特性
1.文本输入:`TextField` 是用于用户输入文本的基本组件。
2.键盘类型:可以通过 `keyboardType` 属性设置输入时弹出的键盘类型,例如数字键盘、邮件键盘等。
3.样式和装饰:通过 `decoration` 属性可以自定义 `TextField` 的外观,包括提示文本、边框、标签等。
4.控制器:使用 `TextEditingController` 来管理和监听文本输入的变化。
5.输入格式化:使用 `inputFormatters` 来限制和格式化用户输入,例如限制输入长度或只允许数字输入。
6.焦点控制:通过 `FocusNode` 可以控制和监听 `TextField` 的焦点状态。
主要属性
- controller: `TextEditingController` 用于读取、设置和监听 `TextField` 的文本。
- focusNode: `FocusNode` 用于管理和监听焦点状态。
- decoration: `InputDecoration` 用于定义 `TextField` 的外观和样式。
- keyboardType: `TextInputType` 确定输入时使用的键盘类型。
- textInputAction: `TextInputAction` 确定操作按钮的类型,如“下一步”或“完成”。
- onChanged: 输入内容更改时的回调函数。
- onSubmitted: 用户提交(按下“完成”键)时的回调函数。
- inputFormatters: `TextInputFormatter` 用于格式化输入文本。
- obscureText: 用于密码输入,隐藏输入的字符。
- maxLength: 限制最大输入长度。
示例代码
以下是一个基本的 `TextField` 示例,展示了如何使用控制器、装饰器和输入格式化器:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class TextFieldExample extends StatefulWidget {
const TextFieldExample({super.key});
@override
_TextFieldExampleState createState() {
return _TextFieldExampleState();
}
}
class _TextFieldExampleState extends State<TextFieldExample> {
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TextField Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _controller,
focusNode: _focusNode,
decoration: const InputDecoration(
labelText: 'Enter your text',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.text,
textInputAction: TextInputAction.done,
onChanged: (text) {
print('Current text: $text');
},
onSubmitted: (text) {
print('Submitted text: $text');
},
inputFormatters: [
LengthLimitingTextInputFormatter(20),
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9]')),
],
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// 打印当前文本框中的文本
print('Final text: ${_controller.text}');
// 移除焦点,使键盘消失
_focusNode.unfocus();
},
child: const Text('Print Text and Unfocus'),
),
],
)),
);
}
}
代码解析
- `TextEditingController`: 用于管理 `TextField` 的文本。可以通过 `_controller.text` 获取或设置输入文本。
- `FocusNode`: 管理 `TextField` 的焦点状态。使用 `_focusNode.unfocus()` 可以移除焦点,通常用于隐藏键盘。
- `InputDecoration`: 自定义 `TextField` 的外观,例如标签文本和边框。
- `inputFormatters`: 使用 `LengthLimitingTextInputFormatter` 限制输入长度,以及 `FilteringTextInputFormatter.allow` 限制输入内容为字母和数字。
- `keyboardType`: 设置为 `TextInputType.text`,用于一般文本输入。
- `textInputAction`: 设置为 `TextInputAction.done`,表示用户完成输入时的操作。
- 事件回调: `onChanged` 和 `onSubmitted` 允许在用户输入或提交时执行操作。
附加功能和用法
- 密码输入: 设置 `obscureText: true` 可以隐藏输入的字符,用于密码输入框。
- 多行输入: 通过设置 `maxLines` 属性,可以允许 `TextField` 输入多行文本。
- 文本样式: 使用 `style` 属性自定义文本的外观,例如字体大小和颜色。
- 错误信息: 使用 `errorText` 属性在 `InputDecoration` 中显示错误信息。
- 自动填充: 在 Android 和 iOS 上,`TextField` 支持自动填充功能,可以通过 `autofillHints` 配置。
Border
在 Flutter 中,`Border` 是一个非常重要的类,用于定义组件(如容器、按钮或输入框)周围的边框。它提供了多种方式来自定义边框的样式、颜色和宽度。理解 `Border` 的各种属性和用法有助于创建更具吸引力和功能性的 UI。
`Border` 类的基本概念
`Border` 类通常用于定义一个组件的四个边的样式。它通常与 `BoxDecoration` 一起使用,以应用于 `Container` 或其他需要边框的组件。
主要属性
- `top`、`bottom`、`left`、`right`: 每个边框的样式,通过 `BorderSide` 定义,可以设置颜色、宽度和样式。
- `all`: 使用同样的边框值应用到所有边。
- `symmetric`: 为水平和垂直方向的边设置对称的边框。
`BorderSide` 类
`BorderSide` 是用来描述边框的样式的类,包含以下属性:
- `color`: 边框的颜色。
- `width`: 边框的宽度,默认为 1.0。
- `style`: 边框的样式,`BorderStyle.solid`(实线)或 `BorderStyle.none`(无边框)。‘’
使用示例
以下是如何使用 `Border` 和 `BorderSide` 来自定义 `Container` 的边框:
import 'package:flutter/material.dart';
class BorderExampleDemo extends StatelessWidget {
const BorderExampleDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Border Example'),
),
body: Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.blue[50],
border: const Border(
top: BorderSide(
color: Colors.blue,
width: 3.0,
style: BorderStyle.solid,
),
bottom: BorderSide(
color: Colors.green,
width: 5.0,
style: BorderStyle.solid,
),
left: BorderSide(
color: Colors.red,
width: 2.0,
style: BorderStyle.solid,
),
right: BorderSide(
color: Colors.orange,
width: 4.0,
style: BorderStyle.solid,
),
)),
),
),
);
}
}
代码解析
- `BoxDecoration`: 用于定义 `Container` 的视觉外观,包括颜色和边框。
- `Border`: 使用 `Border` 类为 `Container` 的每一侧定义不同的 `BorderSide`。
- `BorderSide`: 描述了每个边的颜色、宽度和样式。
常用方法
- `Border.all()`: 创建一个统一的边框,所有边的样式相同。
decoration: BoxDecoration(
border: Border.all(
color: Colors.black,
width: 2.0,
),
),
- `Border.symmetric()`: 为水平(`horizontal`)和垂直(`vertical`)方向定义对称的边框。
decoration: BoxDecoration(
color: Colors.yellow[50],
border: Border.symmetric(
vertical: BorderSide(
color: Colors.red,
width: 4.0,
),
horizontal: BorderSide(
color: Colors.blue,
width: 2.0,
),
),
),
BorderRadius
`BorderRadius` 是 Flutter 中用于定义圆角矩形的类。它可以用于将边框、容器和其他矩形形状的角变圆。`BorderRadius` 提供了多种方法来定义角的圆度,允许你为每个角指定不同的半径,也可以为所有角指定相同的半径。
主要构造方法
1.`BorderRadius.circular(double radius)`: 为所有角设置相同的圆角半径。这个方法是最常用的,适合需要统一圆角的情况。
2.`BorderRadius.all(Radius radius)`: 使用 `Radius` 对象为所有角设置相同的半径。
3.`BorderRadius.only({Radius topLeft, Radius topRight, Radius bottomLeft, Radius bottomRight})`: 分别为每个角设置不同的半径,提供了最大的灵活性。
4.`BorderRadius.horizontal({Radius left, Radius right})`: 仅为水平的两个角设置半径。
5.`BorderRadius.vertical({Radius top, Radius bottom})`: 仅为垂直的两个角设置半径。
示例代码
以下是如何使用 `BorderRadius` 的一些示例:
1. 使用 `BorderRadius.circular`
import 'package:flutter/material.dart';
class CircularBorderExample extends StatelessWidget {
const CircularBorderExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Circular Border Example')),
body: Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.blue[100],
borderRadius: BorderRadius.circular(20.0), // 所有角为圆角 20.0
),
),
),
);
}
}
2. 使用 `BorderRadius.only`
import 'package:flutter/material.dart';
class OnlyBorderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Only Border Example')),
body: Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.green[100],
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30.0),
bottomRight: Radius.circular(30.0),
),
),
),
),
);
}
}
3. 使用 `BorderRadius.horizontal`
import 'package:flutter/material.dart';
class HorizontalBorderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Horizontal Border Example')),
body: Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.red[100],
borderRadius: BorderRadius.horizontal(
left: Radius.circular(20.0),
right: Radius.circular(50.0),
),
),
),
),
);
}
}
EditableText
在 Flutter 中,`EditableText` 是文本输入框的核心控件,所有类似 `TextField` 和 `TextFormField` 的控件都是基于 `EditableText` 封装的。`EditableText` 提供了更底层的能力,允许开发者对文本输入框的行为进行高度自定义。
1. 什么是 `EditableText`?
`EditableText` 是一个可编辑的文本组件,用于实现完全可控的文本输入功能。它不像 `TextField` 那样提供一套高阶封装,而是提供了对文本输入的较底层控制,包括:
- 文本内容的管理
- 焦点的获取与丢失
- 自定义光标、样式、输入法行为
- 输入格式化器和验证规则
2. `EditableText` 的核心属性解析
以下是 `EditableText` 的关键属性及其作用:
必需的属性
属性 | 类型 | 描述 |
`controller` | `TextEditingController` | 管理输入框内容的核心控制器,可监听文本变化或更新文本。 |
`focusNode` | `FocusNode` | 管理输入框焦点状态的节点。可判断输入框是否聚焦并控制焦点行为。 |
`style` | `TextStyle` | 定义输入文本的样式(如字体大小、文本颜色等)。 |
`cursorColor` | `Color` | 设置光标的颜色。 |
`backgroundCursorColor` | `Color` | 光标的背景颜色,当光标不可见时使用该颜色。 |
常用的属性
属性 | 类型 | 描述 |
`obscureText` | `bool` | 是否隐藏输入的文本(常用于密码输入)。 |
`autofocus` | `bool` | 是否在构建时自动获取焦点。 |
`keyboardType` | `TextInputType` | 设置键盘类型(如文本、数字、email等)。 |
`textAlign` | `TextAlign` | 文本的对齐方式(如:左对齐、右对齐、居中)。 |
`maxLines` | `int` | 设置输入框的最大行数,默认为 1。 |
`minLines` | `int` | 设置输入框的最小行数。 |
`inputFormatters` | `List` | 输入格式化器,可用来限制或过滤非法输入(如限制字数、屏蔽特殊字符等)。 |
onChanged` | `ValueChanged` | 当文本内容发生变化时触发的回调函数。 |
`onEditingComplete` | `VoidCallback` | 当用户完成编辑时(如按下键盘的“完成”按钮)触发的回调。 |
`onSubmitted` | `ValueChanged` | 用户提交输入内容时触发的回调。 |
`readOnly` | `bool` | 是否设置为只读模式(用户无法编辑文本)。 |
光标和样式相关属性
属性 | 类型 | 描述 |
`cursorWidth` | `double`
| 光标的宽度。默认值通常为 2.0 像素。 |
`cursorHeight` | `double?` | 光标的高度。通常情况下,光标的高度与文本的高度一致,但你可以通过此属性自定义。 |
`cursorRadius` | `Radius?` | 光标的圆角半径。如果需要圆角光标,可以在这里设置。 |
`cursorOpacityAnimates` | `bool` | 如果为 true,光标的透明度会在显示和隐藏之间动画过渡。 |
`selectionHeightStyle` | `BoxHeightStyle` | 控制文本选择时的高度样式。影响文本选择的外观。 |
`selectionWidthStyle` | `BoxWidthStyle` | 控制文本选择时的宽度样式。通常与 `selectionHeightStyle` 一起使用。 |
其他重要属性
属性 | 类型 | 描述 |
`toolbarOptions` | `ToolbarOptions` | 定义当用户长按输入框时,弹出工具栏中可用的选项(如剪切、复制、粘贴等)。 |
`showCursor` | `bool?` | 控制光标是否显示。 |
`enableInteractiveSelection` | `bool` | 控制是否允许用户交互式选择文本(如长按选择文本)。 |
`textCapitalization` | `TextCapitalization` | 控制输入文本的自动大写行为(如每个单词首字母大写)。 |
使用 `EditableText` 的示例
以下是一个使用 `EditableText` 的完整示例,展示如何自定义一个简单的文本输入框:
import 'package:flutter/material.dart';
class EditableTextWidgetDemo extends StatefulWidget {
const EditableTextWidgetDemo({super.key});
@override
_EditableTextWidgetState createState() {
return _EditableTextWidgetState();
}
}
class _EditableTextWidgetState extends State<EditableTextWidgetDemo> {
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('TextEditingController Example')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: EditableText(
controller: _controller,
focusNode: _focusNode,
style: const TextStyle(color: Colors.black, fontSize: 18.0),
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
keyboardType: TextInputType.text,
autofocus: true,
maxLines: null,
// 允许多行输入
onChanged: (text) {
print("Text changed: $text");
},
),
)));
}
@override
void dispose() {
_controller.dispose();
_focusNode.dispose();
super.dispose();
}
}
总结
- 灵活性: `EditableText` 提供了高度的灵活性和定制能力。虽然 `TextField` 和 `TextFormField` 已经满足了大多数日常需求,但 `EditableText` 允许开发者完全控制文本输入的细节。如果你需要实现一个非常定制化的文本输入体验,`EditableText` 是一个很好的起点。
- 自定义控制: 通过 `EditableText`,你可以完全自定义光标的外观、文本样式、输入格式化、焦点行为、输入法行为等等。这使得它适合于需要特定文本输入行为的应用场景。
- 底层实现: 作为 `TextField` 和 `TextFormField` 的底层实现,`EditableText` 需要开发者手动管理 `TextEditingController` 和 `FocusNode`,这意味着开发者必须更加关注资源的管理(例如确保在不需要时正确释放这些对象)。
- 应用场景: 使用 `EditableText` 适用于需要精细控制用户输入的场景,比如自定义的文本编辑器、需要特殊输入验证的表单等。
通过了解和善用 `EditableText`,开发者可以创建更具个性化的用户输入界面,满足特定的应用需求。希望这个解析能够帮助你更好地理解和使用 `EditableText`。
GestureDetector
`GestureDetector` 是 Flutter 中一个非常重要的组件,用于检测用户的手势操作(如点击、双击、拖动、滑动等)。通过使用 `GestureDetector`,你可以捕获用户在屏幕上的各种手势,并对这些手势做出响应。这使得 `GestureDetector` 成为构建交互式用户界面的关键工具之一。
主要功能
`GestureDetector` 提供了一种简单而强大的方式来监听和响应用户的手势。以下是一些常用的手势检测功能:
点击手势:
- `onTap`: 用户点击时触发。
- `onDoubleTap`: 用户双击时触发。
- `onLongPress`: 用户长按时触发。
拖动手势:
- `onPanStart`: 用户开始拖动时触发。
- `onPanUpdate`: 用户拖动过程中触发。
- `onPanEnd`: 用户拖动结束时触发。
滑动手势:
- `onHorizontalDragStart`, `onHorizontalDragUpdate`, `onHorizontalDragEnd`: 检测水平拖动。
- `onVerticalDragStart`, `onVerticalDragUpdate`, `onVerticalDragEnd`: 检测垂直拖动。
缩放手势:
- `onScaleStart`, `onScaleUpdate`, `onScaleEnd`: 用于检测缩放操作,通常用于实现捏合缩放功能。
基本用法
以下是一个简单的示例,展示如何使用 `GestureDetector` 来监听不同的手势:
import 'package:flutter/material.dart';
class GestureDetectorExampleMyTestDemo extends StatelessWidget {
const GestureDetectorExampleMyTestDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('GestureDetector Example')),
body: Center(
child: GestureDetector(
onTap: () {
print('Tapped!');
},
onDoubleTap: () {
print('Double Tapped!');
},
onLongPress: () {
print('Long Pressed!');
},
onPanUpdate: (details) {
print('Pan Updated: ${details.delta}');
},
child: Container(
width: 200,
height: 200,
color: Colors.blue,
alignment: Alignment.center,
child: const Text(
'Tap Me',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
);
}
}
详细属性解析
- `onTap`: 单次点击时的回调。
- `onDoubleTap`: 双击时的回调。
- `onLongPress`: 长按时的回调。
- `onPanStart`, `onPanUpdate`, `onPanEnd`: 分别用于开始、更新和结束拖动时的回调。
- `onHorizontalDragStart`, `onHorizontalDragUpdate`, `onHorizontalDragEnd`: 处理水平拖动。
- `onVerticalDragStart`, `onVerticalDragUpdate`, `onVerticalDragEnd`: 处理垂直拖动。
- `onScaleStart`, `onScaleUpdate`, `onScaleEnd`: 处理缩放手势。
注意事项
1. Hit Testing(命中测试)
- `GestureDetector` 需要一个非空的子组件才能检测手势。
- 如果 `GestureDetector` 的子组件是空的(比如没有子组件,或者子组件是 `Container` 且未设置 `color` 属性),手势检测将无法生效。这是因为 Flutter 默认不会为空组件进行命中测试。
- 解决方案:可以为 `GestureDetector` 的子组件设置一个明确的背景颜色(即使是透明的),例如 `color: Colors.transparent`。
GestureDetector(
onTap: () {
print('Tapped');
},
child: Container(
width: 100,
height: 100,
color: Colors.transparent, // 必须设置颜色,否则点击事件可能无法检测
),
)
2. 手势冲突
- 当多个手势识别器(如拖动和缩放)同时应用于同一个组件时,可能会发生手势冲突。
- Flutter 提供了 手势竞技场机制(Gesture Arena) 来解决冲突。在默认情况下,多个手势识别器会竞争事件的优先权,只有一个手势识别器会胜出。
- 如果你需要同时响应多个手势,可以使用 `RawGestureDetector` 或 `GestureRecognizer` 来自定义手势行为。
例如,通过 `onScaleUpdate` 实现拖动和缩放:
GestureDetector(
onScaleUpdate: (details) {
print('Scale: ${details.scale}, Translation: ${details.focalPointDelta}');
},
child: Container(
width: 200,
height: 200,
color: Colors.blue,
),
);
3. 嵌套手势
- 当 `GestureDetector` 嵌套时,内部的手势可能会覆盖外部的手势,或者导致手势冲突。
- Flutter 提供了 `Behavior` 属性来控制手势的传播方式:
- `HitTestBehavior.deferToChild`(默认值):只有子组件能够响应手势时,`GestureDetector` 才会检测手势。
- `HitTestBehavior.opaque`:即使子组件是透明的,父组件也会参与命中测试。
- `HitTestBehavior.translucent`:父组件会响应手势,但透明区域的子组件仍然可以响应手势。
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
print('Parent tapped!');
},
child: GestureDetector(
onTap: () {
print('Child tapped!');
},
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
);
Flutter实现输入验证码代码学习
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
///验证码输入框
class VerificationCodeInputDemoPage extends StatelessWidget {
const VerificationCodeInputDemoPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("VerificationCodeInputDemoPage"),
),
body: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Center(
child: VerCodeInput(
ctx: context,
length: 6,
keyboardType: TextInputType.number,
builder: staticRectangle(context),
onChanged: (value) {
},
///输入完成时
onFilled: (value) {
//print('Your input is $value.');
},
),
),
),
);
}
staticRectangle(BuildContext context) {
var codeSize = 6;
double padding = 16;
double width = MediaQuery.sizeOf(context).width;
double codeFullSize = ((width - 2 * padding) / codeSize);
double codeNormalSize = codeFullSize - 20;
return CodeInputBuilders.rectangle(
totalSize: Size(codeFullSize, codeFullSize),
emptySize: Size(codeNormalSize, codeNormalSize),
filledSize: Size(codeNormalSize, codeNormalSize),
borderRadius: BorderRadius.zero,
border: Border.all(color: Theme.of(context).primaryColor, width: 1.0),
color: Colors.transparent,
textStyle: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 16.0,
fontWeight: FontWeight.bold));
}
}
///from https://github.com/tiny-express/flutter_verification_code_input/blob/master/lib/src/verification_code_input.dart
typedef CodeInputBuilder = Widget Function(bool hasFocus, String char);
class VerCodeInput extends StatefulWidget {
const VerCodeInput._({
super.key,
required this.length,
required this.keyboardType,
required this.inputFormatters,
required this.builder,
required this.ctx,
this.onChanged,
this.onFilled,
});
factory VerCodeInput({
Key? key,
required int length,
TextInputType keyboardType = TextInputType.text,
List<TextInputFormatter>? inputFormatters,
BuildContext? ctx,
required CodeInputBuilder builder,
void Function(String value)? onChanged,
void Function(String value)? onFilled,
}) {
assert(length > 0, 'The length needs to be larger than zero.');
assert(length.isFinite, 'The length needs to be finite.');
inputFormatters ??= _createInputFormatters(length, keyboardType);
return VerCodeInput._(
key: key,
length: length,
keyboardType: keyboardType,
inputFormatters: inputFormatters,
builder: builder,
ctx: ctx,
onChanged: onChanged,
onFilled: onFilled,
);
}
/// The length of character entities to always display.
///
/// ## Sample code
///
/// A code input with 4 characters:
///
/// ```dart
/// CodeInput(length: 4)
/// ```
final int length;
/// The type of thconstard which shows up.
///
/// ## Sample codeconst
///
/// ```dart
/// CodeInput(keyboardType: TextInputType.number)
/// ```
final TextInputType keyboardType;
/// A list of input formatters which can validate the text as it is being
/// typed.
///
/// If you specify this parameter, the default input formatters aren't used,
/// so make sure you really check for everything (like length of the input).
///
/// ## Sample code
///
/// An code input that displays a normal keyboard but only allows for
/// hexadecimal input:
///
/// ```dart
/// CodeInput(
/// inputFormatters: [
/// WhitelistingTextInputFormatter(RegExp('^[0-9a-fA-F]*\$'))
/// ]
/// )
/// ```
final List<TextInputFormatter> inputFormatters;
/// A builder for the character entities.
///
/// See [CodeInputBuilders] for examples.
final CodeInputBuilder builder;
/// A callback for changes to the input.
final void Function(String value)? onChanged;
/// A callback for when the input is filled.
final void Function(String value)? onFilled;
/// context parent because of MediaQuery.of(widget.ctx)
final BuildContext? ctx;
/// A helping function that creates input formatters for a given length and
/// keyboardType.
static List<TextInputFormatter> _createInputFormatters(
int length, TextInputType keyboardType) {
final formatters = <TextInputFormatter>[
LengthLimitingTextInputFormatter(length)
];
// Add keyboard specific formatters.
// For example, a code input with a number keyboard type probably doesn't
// want to allow decimal separators or signs.
if (keyboardType == TextInputType.number) {
formatters.add(FilteringTextInputFormatter.digitsOnly);
}
return formatters;
}
@override
_VerCodeInputState createState() => _VerCodeInputState();
}
class _VerCodeInputState extends State<VerCodeInput> {
final node = FocusNode();
final controller = TextEditingController();
String get text => controller.text;
@override
Widget build(BuildContext context) {
// We'll display the visual widget and a not shown EditableText for doing
// the actual work on top of each other.
return Stack(children: <Widget>[
// This is the actual EditableText wrapped in a Container with zero
// dimensions.
SizedBox(
width: 0.0,
height: 0.0,
child: EditableText(
controller: controller,
focusNode: node,
inputFormatters: widget.inputFormatters,
keyboardType: widget.keyboardType,
backgroundCursorColor: Colors.black,
style: const TextStyle(),
// Doesn't really matter.
cursorColor: Colors.black,
// Doesn't really matter.
onChanged: (value) => setState(() {
widget.onChanged?.call(value);
if (value.length == widget.length) {
widget.onFilled?.call(value);
}
}),
)),
// These are the actual character widgets. A transparent container lies
// right below the gesture detector, so all taps get collected, even
// the ones between the character entities.
GestureDetector(
onTap: () {
if (MediaQuery.viewInsetsOf(context).bottom == 0) {
final focusScope = FocusScope.of(context);
focusScope.requestFocus(FocusNode());
Future.delayed(
Duration.zero, () => focusScope.requestFocus(node));
}
},
child: Container(
color: Colors.transparent,
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(widget.length, (i) {
final hasFocus = controller.selection.start == i;
final char = i < text.length ? text[i] : '';
final characterEntity = widget.builder(hasFocus, char);
return characterEntity;
}),
),
)),
]);
}
}
/// An abstract class that provides some commonly-used builders for the
/// character entities.
///
/// * [containerized]: A builder putting chars in an animated container.
/// * [circle]: A builder putting chars in circles.
/// * [rectangle]: A builder putting chars in rectangles.
/// * [lightCircle]: A builder putting chars in light circles.
/// * [darkCircle]: A builder putting chars in dark circles.
/// * [lightRectangle]: A builder putting chars in light rectangles.
/// * [darkRectangle]: A builder putting chars in dark rectangles.
abstract class CodeInputBuilders {
/// Builds the input inside an animated container.
static CodeInputBuilder containerized({
Duration animationDuration = const Duration(milliseconds: 50),
required Size totalSize,
required Size emptySize,
required Size filledSize,
required BoxDecoration emptyDecoration,
required BoxDecoration filledDecoration,
required TextStyle emptyTextStyle,
required TextStyle filledTextStyle,
}) {
return (bool hasFocus, String char) => Container(
width: totalSize.width,
height: totalSize.height,
alignment: Alignment.center,
child: AnimatedContainer(
duration: const Duration(milliseconds: 100),
decoration: char.isEmpty ? emptyDecoration : filledDecoration,
width: char.isEmpty ? emptySize.width : filledSize.width,
height: char.isEmpty ? emptySize.height : filledSize.height,
alignment: Alignment.center,
child: Text(char,
style: char.isEmpty ? emptyTextStyle : filledTextStyle),
));
}
/// Builds the input inside a circle.
static CodeInputBuilder circle(
{double totalRadius = 30.0,
double emptyRadius = 10.0,
double filledRadius = 25.0,
required Border border,
required Color color,
required TextStyle textStyle}) {
final decoration = BoxDecoration(
shape: BoxShape.circle,
border: border,
color: color,
);
return containerized(
totalSize: Size.fromRadius(totalRadius),
emptySize: Size.fromRadius(emptyRadius),
filledSize: Size.fromRadius(filledRadius),
emptyDecoration: decoration,
filledDecoration: decoration,
emptyTextStyle: textStyle.copyWith(fontSize: 0.0),
filledTextStyle: textStyle);
}
/// Builds the input inside a rectangle.
static CodeInputBuilder rectangle({
Size totalSize = const Size(50.0, 60.0),
Size emptySize = const Size(20.0, 20.0),
Size filledSize = const Size(40.0, 60.0),
BorderRadius borderRadius = BorderRadius.zero,
required Border border,
required Color color,
required TextStyle textStyle,
}) {
final decoration = BoxDecoration(
border: border,
borderRadius: borderRadius,
color: color,
);
return containerized(
totalSize: totalSize,
emptySize: emptySize,
filledSize: filledSize,
emptyDecoration: decoration,
filledDecoration: decoration,
emptyTextStyle: textStyle.copyWith(fontSize: 0.0),
filledTextStyle: textStyle);
}
/// Builds the input inside a light circle.
static CodeInputBuilder lightCircle({
double totalRadius = 30.0,
double emptyRadius = 10.0,
double filledRadius = 25.0,
}) {
return circle(
totalRadius: totalRadius,
emptyRadius: emptyRadius,
filledRadius: filledRadius,
border: Border.all(color: Colors.white, width: 2.0),
color: Colors.white10,
textStyle: const TextStyle(
color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.bold));
}
/// Builds the input inside a light circle.
static CodeInputBuilder darkCircle({
double totalRadius = 30.0,
double emptyRadius = 10.0,
double filledRadius = 25.0,
}) {
return circle(
totalRadius: totalRadius,
emptyRadius: emptyRadius,
filledRadius: filledRadius,
border: Border.all(color: Colors.black, width: 2.0),
color: Colors.black12,
textStyle: const TextStyle(
color: Colors.black, fontSize: 20.0, fontWeight: FontWeight.bold));
}
/// Builds the input inside a light rectangle.
static CodeInputBuilder lightRectangle({
Size totalSize = const Size(50.0, 60.0),
Size emptySize = const Size(20.0, 20.0),
Size filledSize = const Size(40.0, 60.0),
BorderRadius borderRadius = BorderRadius.zero,
}) {
return rectangle(
totalSize: totalSize,
emptySize: emptySize,
filledSize: filledSize,
borderRadius: borderRadius,
border: Border.all(color: Colors.white, width: 2.0),
color: Colors.white10,
textStyle: const TextStyle(
color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.bold));
}
static CodeInputBuilder staticRectangle({
Size totalSize = const Size(60.0, 60.0),
Size emptySize = const Size(40.0, 40.0),
Size filledSize = const Size(40.0, 40.0),
BorderRadius borderRadius = BorderRadius.zero,
}) {
return rectangle(
totalSize: totalSize,
emptySize: emptySize,
filledSize: filledSize,
borderRadius: borderRadius,
border: Border.all(color: Colors.white, width: 1.0),
color: Colors.transparent,
textStyle: const TextStyle(
color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.bold));
}
/// Builds the input inside a dark rectangle.
static CodeInputBuilder darkRectangle({
Size totalSize = const Size(50.0, 60.0),
Size emptySize = const Size(20.0, 20.0),
Size filledSize = const Size(40.0, 60.0),
BorderRadius borderRadius = BorderRadius.zero,
}) {
return rectangle(
totalSize: totalSize,
emptySize: emptySize,
filledSize: filledSize,
borderRadius: borderRadius,
border: Border.all(color: Colors.black, width: 2.0),
color: Colors.black12,
textStyle: const TextStyle(
color: Colors.black, fontSize: 20.0, fontWeight: FontWeight.bold));
}
}