学习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});@overrideWidget 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: Robotofonts:- asset: fonts/Roboto-Regular.ttf- asset: fonts/Roboto-Bold.ttfweight: 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});@overrideWidget 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;@overrideWidget 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});@overrideWidget 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});@overrideWidget 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 {@overrideTextEditingValue 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();@overrideWidget 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});@overrideWidget 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});@overrideWidget 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 {@overrideWidget 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 {@overrideWidget 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();@overrideWidget 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");},),)));}@overridevoid 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});@overrideWidget 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});@overrideWidget 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.darttypedef 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;@overrideWidget 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));}
}