前置知识点学习
GlobalKey
`GlobalKey` 是 Flutter 中一个非常重要的概念,它用于唯一标识 widget 树中的特定 widget,并提供对该 widget 的访问。这在需要跨越 widget 树边界进行交互或在 widget 树重建时保持状态时尤其有用。
`GlobalKey` 的作用
- 唯一标识:`GlobalKey` 可以在 widget 树中唯一标识一个 widget 实例。这在需要在多个地方引用同一个 widget 时特别有用。
- 访问状态:可以通过 `GlobalKey` 访问 `StatefulWidget` 的状态对象。这允许你在 widget 树之外操作该 widget 的状态。
- 在重建时保持状态:当 widget 树重建时,`GlobalKey` 可以确保与该 key 关联的 widget 保持其状态不变。通常情况下,Flutter 会根据位置和类型重新创建 widget,但使用 `GlobalKey` 可以避免这种情况。
-
跨越 widget 树边界的操作:可以用于在 widget 树的一个部分与另一个部分之间传递信息或触发操作,这在复杂的 UI 中非常有用。
`GlobalKey` 案例
import 'package:flutter/material.dart';
class MyGlobalKeyPage extends StatelessWidget {
MyGlobalKeyPage({super.key});
final GlobalKey<_MySimpleWidgetState> _key =
GlobalKey<_MySimpleWidgetState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("MyBubbleDemoPage"),
),
body: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
margin: const EdgeInsets.all(15),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MySimpleWidget(key: _key),
ElevatedButton(
onPressed: () {
// 閫氳繃 GlobalKey 璁块棶 MyWidget 鐨勭姸鎬侊紝骞惰皟鐢ㄥ叾鏂规硶
_key.currentState?.changeText();
},
child: Text('Call Method on MySimpleWidget'),
),
],
),
),
));
}
}
class MySimpleWidget extends StatefulWidget {
const MySimpleWidget({super.key});
@override
_MySimpleWidgetState createState() {
return _MySimpleWidgetState();
}
}
class _MySimpleWidgetState extends State<MySimpleWidget> {
String _text = "Hello GlobalKey";
@override
Widget build(BuildContext context) {
return Text(_text);
}
void changeText() {
setState(() {
_text = "Hello TEXT has changed";
});
`GlobalKey`在示例中的作用
- 标识和访问:`GlobalKey` 用于标识 `MySimpleWidget` 实例,并通过 `_key.currentState` 访问其状态和方法。
- 跨 widget 树操作:通过 `ElevatedButton`,我们可以从 `MyGlobalKeyPage
` 中访问并操作 `MySimpleWidget` 的状态。
注意事项
- 唯一性:`GlobalKey` 必须在 widget 树中唯一使用。多个 widget 共享同一个 `GlobalKey` 会导致异常。
- 性能开销:由于 `GlobalKey` 会阻止 widget 的优化重建,过多使用可能影响性能。应在必要时使用。
- 避免滥用:虽然 `GlobalKey` 很强大,但应尽量在局部范围内使用,避免在整个应用中广泛使用。
通过正确使用 `GlobalKey`,你可以在 Flutter 应用中实现更复杂的状态管理和交互逻辑。它为在 widget 树结构中实现灵活的组件通信提供了强有力的支持。
flutter报错
Can't define the 'const' constructor because the field '_key' is initialized with a non-constant value.
这个错误信息是因为在 Flutter 中,你试图在一个 `const` 构造函数中初始化一个字段,而这个字段被赋予了一个非常量的值。
问题原因
在 Flutter 中,`const` 构造函数要求所有字段都用常量值初始化。`GlobalKey` 和其他类似的对象在 Dart 中不是常量,因为它们在运行时创建并分配内存。因此,它们不能在 `const` 构造函数中被用作初始值。
解决方案
要解决这个问题,你需要确保在构造函数中,不要将 `GlobalKey` 或其他运行时生成的对象用于 `const` 构造函数。通常的做法是将这些对象作为普通的变量,而不是在 `const` 构造函数中初始化。
示例修正
假设有以下代码导致了这个错误:
class MyWidget extends StatelessWidget {
final GlobalKey _key = GlobalKey(); // 这里不能使用 const
const MyWidget({Key? key}) : super(key: key); // 错误的用法
}
可以通过以下方式修正:
class MyWidget extends StatelessWidget {
final GlobalKey _key = GlobalKey(); // 不使用 const
MyWidget({Key? key}) : super(key: key); // 修改为非 const 构造函数
}
或者,如果你确实需要 `MyWidget` 是一个常量构造函数,你需要移除 `GlobalKey` 的初始化:
class MyWidget extends StatelessWidget {
final GlobalKey _key;
const MyWidget({Key? key, required GlobalKey keyParam})
: _key = keyParam, // 通过参数传递而不是初始化
super(key: key);
}
然后在使用 `MyWidget` 时,传递一个 `GlobalKey` 实例:
final myKey = GlobalKey();
MyWidget(key: myKey, keyParam: myKey);
总结
- `const` 构造函数中的字段必须是常量:确保所有字段初始化都是常量表达式。
- `GlobalKey` 不能是常量:因为它们在运行时创建,所以不能在 `const` 构造函数中直接初始化。
- 通过参数传递可变对象:如果需要使用 `const` 构造函数,可以通过参数将 `GlobalKey` 传递进去,而不是在类内部直接初始化。
常量构造函数
在 Flutter 中,构造函数用于初始化类的实例。`const MyWidget({Key? key}) : super(key: key);` 是一个常量构造函数的示例,用于初始化 `MyWidget` 类的实例。让我们逐步解析这个构造函数的含义:
`const` 关键字
- 常量构造函数:使用 `const` 关键字定义的构造函数称为常量构造函数。它允许在编译时创建不可变的常量实例。
- 优化:在使用常量构造函数初始化的对象时,如果这些对象的所有字段都是常量,Flutter 可以在编译时对这些对象进行优化,以减少内存使用和提高性能。
`MyWidget({Key? key})`
- 命名构造函数:`MyWidget` 是类的构造函数。括号中的 `{Key? key}` 是一个可选的命名参数。
- `Key` 参数:`Key` 是 Flutter 中用来标识 widget 的唯一标识符,用于在 widget 树更新时保持 widget 的状态。`Key?` 表示这个参数是可选的,并且可以为 null。
`: super(key: key)`
- 初始化列表:冒号 `:` 后面的是初始化列表,用于在构造函数体执行之前初始化父类的字段。
- `super(key: key)`:调用父类(`StatelessWidget` 或 `StatefulWidget`)的构造函数,并传递 `key` 参数。这是因为 `StatelessWidget` 和 `StatefulWidget` 的父类 `Widget` 定义了一个 `key` 参数,用于管理 widget 的标识。
SingleTickerProviderStateMixin
`SingleTickerProviderStateMixin` 是 Flutter 中提供的一种混合(mixin),用于创建动画时管理 `Ticker` 的生命周期。它通常与 `StatefulWidget` 一起使用,以有效地处理动画帧。
什么是 `Ticker`?
在 Flutter 中,`Ticker` 是一个触发动画的计时器,每帧都会调用一次回调函数。它类似于一个心跳信号,让动画在每个屏幕刷新周期内前进一小步。
`SingleTickerProviderStateMixin` 的作用
- 管理 `Ticker`:`SingleTickerProviderStateMixin` 使 `State` 类成为一个 `TickerProvider`,这意味着它能够提供 `Ticker` 对象。这个 `Ticker` 用于驱动动画。
- 高效地管理资源:通过提供一个 `Ticker`,`SingleTickerProviderStateMixin` 帮助确保动画在不需要时被正确地停止和释放资源。
- 简单便捷:使用这个 mixin,可以轻松创建一个 `AnimationController`,而不需要手动管理 `Ticker` 的生命周期。
使用场景
通常在需要创建动画时,你会在 `StatefulWidget` 的 `State` 类中使用它。以下是一个基本的使用示例:
import 'package:flutter/material.dart';
class MyAnimateWidgetPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("MyBubbleDemoPage"),
),
body: Container(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
margin: const EdgeInsets.all(15),
child: const MyAnimateWidget(),
));
}
}
class MyAnimateWidget extends StatefulWidget {
const MyAnimateWidget({super.key});
@override
_MyAnimateWidgetState createState() => _MyAnimateWidgetState();
}
class _MyAnimateWidgetState extends State<MyAnimateWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: FadeTransition(
opacity: _controller,
child: const FlutterLogo(size: 100.0),
),
),
);
}
@override
void initState() {
super.initState();
//vsync: this => 需要一个 TickerProvider,这里就是 SingleTickerProviderStateMixin 提供的
_controller =
AnimationController(vsync: this, duration: const Duration(seconds: 2));
_controller.repeat(reverse: true);
}
@override
void dispose() {
// 记得在 dispose 方法中释放 AnimationController
_controller.dispose();
super.dispose();
}
}
代码解析
- `with SingleTickerProviderStateMixin`:将 `SingleTickerProviderStateMixin` 添加到 `State` 类中,使其成为一个 `TickerProvider`。
- `vsync: this`:在创建 `AnimationController` 时,`vsync` 参数需要一个 `TickerProvider`。这里通过 `this` 传递当前 `State` 实例。
- `_controller.repeat(reverse: true)`:启动动画控制器,使动画在两秒钟内从开始到结束,然后反向重复。
- `dispose` 方法:在组件销毁时,调用 `_controller.dispose()` 释放资源,防止内存泄漏。
`SingleTickerProviderStateMixin` 是一个便捷的工具,用于在 Flutter 中实现动画。它让 `State` 类能够提供 `Ticker`,从而驱动动画控制器。通过这种方式,你可以轻松地创建高效且可管理的动画效果。对于需要多个 `Ticker` 的复杂场景,可以考虑使用 `TickerProviderStateMixin`。
MediaQuery.sizeOf(context)
`MediaQuery.sizeOf(context)` 是在 Flutter 中用于获取当前屏幕或父 widget 可用空间大小的一个方法。这是 Flutter 3.7 引入的一个静态方法,用于简化对 `MediaQuery` 的访问。
用途
- 获取屏幕尺寸:它返回一个 `Size` 对象,包含当前设备屏幕的宽度和高度。
- 适配布局:在构建响应式布局时,可以使用这个方法来获取屏幕尺寸并调整 widget 的大小和位置。
工作原理
- `BuildContext`:它是 `MediaQuery` 用于查找 widget 树中与之关联的 `MediaQueryData` 对象的上下文。
- `MediaQuery.sizeOf(context)`:该方法通过 `BuildContext` 获取 `MediaQueryData`,然后从中提取屏幕尺寸信息。
使用示例
import 'package:flutter/material.dart';
class MyResponsiveWidgetPage extends StatelessWidget {
const MyResponsiveWidgetPage({super.key});
@override
Widget build(BuildContext context) {
// 使用 MediaQuery.sizeOf(context) 获取屏幕尺寸
Size screenSize = MediaQuery.sizeOf(context);
return Scaffold(
appBar: AppBar(title: Text('Responsive Example')),
body: Center(
child: Container(
width: screenSize.width * 0.8, // 宽度是屏幕宽度的 80%
height: screenSize.height * 0.5, // 高度是屏幕高度的 50%
color: Colors.blue,
child: Center(
child: Text(
'Responsive Container',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
),
),
);
}
}
注意事项
- BuildContext 的位置:`MediaQuery.sizeOf(context)` 所用的 `context` 必须是 widget 树中包含 `MediaQuery` 的上下文。通常这意味着它是在 `MaterialApp` 或 `CupertinoApp` 的子级中。
- 响应式设计:虽然可以简单地使用这个方法来适配屏幕大小,但在复杂的布局中,考虑使用其他响应式设计工具,如 `LayoutBuilder` 或 `FractionallySizedBox`,以更好地适配各种屏幕尺寸。
- 更新:请确保你的 Flutter SDK 版本支持 `MediaQuery.sizeOf(context)`,因为这是一种相对较新的方法。
通过使用 `MediaQuery.sizeOf(context)`,你可以轻松地访问设备的屏幕尺寸信息,从而为用户提供更好的响应式界面布局。
import 'package:flutter/material.dart';
class MyResponsiveWidgetPage extends StatelessWidget {
const MyResponsiveWidgetPage({super.key});
@override
Widget build(BuildContext context) {
// 使用 MediaQuery.sizeOf(context) 获取屏幕尺寸
Size screenSize = MediaQuery.sizeOf(context);
return Scaffold(
appBar: AppBar(title: Text('Responsive Example')),
body: Center(
child: Container(
width: screenSize.width * 0.8, // 宽度是屏幕宽度的 80%
height: screenSize.height * 0.5, // 高度是屏幕高度的 50%
color: Colors.blue,
child: Center(
child: Text(
'Responsive Container',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
),
),
);
}
}
GestureDetector
`GestureDetector` 是 Flutter 中一个非常重要的组件,用于检测用户在设备屏幕上的手势。它提供了一种简单的方法来监听并响应用户的触摸、拖动、点击等交互事件。
主要功能
`GestureDetector` 提供了一系列回调函数,允许你处理不同类型的手势。以下是一些常见的手势和对应的回调:
点击手势:
- `onTap`: 用户轻触屏幕时触发。
- `onDoubleTap`: 用户快速连续点击两次时触发。
- `onLongPress`: 用户长按屏幕时触发。
拖动手势:
- `onPanStart`: 用户开始拖动时触发。
- `onPanUpdate`: 用户拖动时持续触发。
- `onPanEnd`: 用户拖动结束时触发。
缩放手势:
- `onScaleStart`: 缩放手势开始时触发。
- `onScaleUpdate`: 缩放手势更新时持续触发。
- `onScaleEnd`: 缩放手势结束时触发。
其他手势:
- `onVerticalDragStart`, `onVerticalDragUpdate`, `onVerticalDragEnd`: 垂直拖动相关的手势。
- `onHorizontalDragStart`, `onHorizontalDragUpdate`, `onHorizontalDragEnd`: 水平拖动相关的手势。
使用示例
import 'package:flutter/material.dart';
class GestureExamplePage extends StatelessWidget {
const GestureExamplePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('GestureDetector Example')),
body: Center(
child: GestureDetector(
onTap: () {
print('Container tapped!');
},
onPanUpdate: (details) {
print('Dragging: ${details.localPosition}');
},
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(
child: Text('Tap or drag me!',
style: TextStyle(color: Colors.white, fontSize: 16)),
),
),
),
),
);
}
}
细节与注意事项
- 透明度:`GestureDetector` 默认只会在非透明的地方响应手势。如果你需要在透明区域也检测手势,可以设置 `behavior: HitTestBehavior.translucent`。
- 优先级:如果多个手势检测器重叠,Flutter 会根据其内部的手势识别器机制来确定哪个手势优先处理。
- 组合手势:`GestureDetector` 可以同时检测多个手势,例如你可以同时监听 `onTap` 和 `onDoubleTap`,但需要注意可能的冲突。
- 性能:在复杂的布局中,需要注意手势检测的性能开销。尽量在需要的地方使用 `GestureDetector`,避免过多的嵌套。
- 默认行为:`GestureDetector` 不会改变子组件的外观或行为,它仅提供手势识别能力,你需要在回调函数中定义具体行为。
通过 `GestureDetector`,Flutter 开发者可以轻松实现与用户的交互,处理各种复杂的手势需求,从而增强应用的用户体验。
onPanUpdate
`onPanUpdate` 是 `GestureDetector` 组件中的一个回调,用于处理用户的拖动(或平移)手势。当用户在屏幕上拖动时,`onPanUpdate` 会持续触发,并提供有关拖动事件的信息。
主要特性
- 持续触发:当用户在屏幕上拖动时,每次移动都会触发 `onPanUpdate`,这使得你可以跟踪拖动路径的每一个点。
- 事件细节:回调函数接收一个 `DragUpdateDetails` 对象,它包含有关拖动的详细信息。
`DragUpdateDetails` 的重要属性
- `globalPosition`:用户触摸点相对于整个屏幕的坐标。
- `localPosition`:用户触摸点相对于容器的坐标(即触摸点在检测手势的组件内的坐标)。
- `delta`:用户自上次更新以来移动的距离偏移量。可以用来计算拖动的速度或方向。
- `primaryDelta`:如果拖动是单向的(水平或垂直),这将返回沿主要轴的偏移量。
使用示例
import 'package:flutter/material.dart';
class PanUpdateExamplePage extends StatelessWidget {
const PanUpdateExamplePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('onPanUpdate Example')),
body: const Center(
child: PanUpdateExampleWidget(),
),
);
}
}
class PanUpdateExampleWidget extends StatefulWidget {
const PanUpdateExampleWidget({super.key});
@override
_PanUpdateExampleState createState() {
return _PanUpdateExampleState();
}
}
class _PanUpdateExampleState extends State<PanUpdateExampleWidget> {
Offset _position = Offset.zero;
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
_position += details.delta;
});
},
child: Stack(
children: [
Positioned(
left: _position.dx,
top: _position.dy,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: const Center(
child: Text(
'Drag me',
style: TextStyle(color: Colors.white),
),
),
))
],
),
);
}
}
onPanStart
`onPanStart` 是 `GestureDetector` 组件中的一个回调,用于处理用户的拖动(或平移)手势的开始事件。当用户在屏幕上开始拖动时,`onPanStart` 会被触发。它是实现拖动交互的第一步,通常与 `onPanUpdate` 和 `onPanEnd` 一起使用来处理完整的拖动事件。
主要特性
- 触发时机:当用户用手指触摸屏幕并开始拖动时立即触发。
- 事件细节:回调函数接收一个 `DragStartDetails` 对象,提供有关手势开始的详细信息。
`DragStartDetails` 的重要属性
- `globalPosition`:用户触摸点相对于整个屏幕的坐标。
- `localPosition`:用户触摸点相对于手势检测区域的坐标(即触摸点在 `GestureDetector` 的子组件内的坐标)。
使用案例
import 'package:flutter/material.dart';
class PanStartExamplePage extends StatelessWidget {
const PanStartExamplePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('onPanStart Example')),
body: const Center(
child: PanStartExampleWidget(),
),
);
}
}
class PanStartExampleWidget extends StatefulWidget {
const PanStartExampleWidget({super.key});
@override
_PanStartExampleState createState() {
return _PanStartExampleState();
}
}
class _PanStartExampleState extends State<PanStartExampleWidget> {
Offset _startPosition = Offset.zero;
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanStart: (details) {
setState(() {
_startPosition = details.localPosition;
});
},
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(
child: Text('Start Position: $_startPosition',
style: const TextStyle(color: Colors.white)),
),
),
);
}
}
CustomPaint
`CustomPaint` 是 Flutter 中的一个强大组件,用于在屏幕上自定义绘制内容。通过 `CustomPaint`,你可以在 Flutter 应用中创建复杂的图形和视觉效果,超越标准的 UI 组件。
主要组件
- `CustomPaint`:这是一个 widget,它包含一个 `painter` 和一个 `child`。`painter` 用于自定义绘制,`child` 是可选的,在绘制内容之上显示。
- `CustomPainter`:一个抽象类,需要实现其中的 `paint` 和 `shouldRepaint` 方法。`paint` 方法定义了具体的绘制逻辑,而 `shouldRepaint` 决定了何时需要重绘。
关键方法
- paint(Canvas canvas, Size size)`:在此方法中实现具体的绘制逻辑。通过 `Canvas` 对象在给定的 `Size` 上绘制图形。
- `shouldRepaint(CustomPainter oldDelegate)`:返回一个布尔值,指示当 `CustomPaint` 的配置变化时是否需要重绘。通常在绘制逻辑或者输入参数变化时返回 `true`。
使用案例
import 'package:flutter/material.dart';
class CirclePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2;
canvas.drawCircle(center, radius, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false; // 如果绘制内容不变,返回 false 以提高性能
}
}
import 'package:flutter/material.dart';
import 'package:gsy_flutter_demo/widget/circle_painter.dart';
class CustomPaintExamplePage extends StatelessWidget {
const CustomPaintExamplePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('CustomPaint Example')),
body: Center(
child: CustomPaint(
size: const Size(100, 100),
painter: CirclePainter(),
),
),
);
}
}
注意事项
- 性能:由于自定义绘制可能涉及大量计算,因此要小心处理绘制逻辑,确保 `shouldRepaint` 返回正确的值以避免不必要的重绘。
- 绘制顺序:`CustomPaint` 的 `child` 会在 `painter` 绘制之后显示,这意味着绘制的内容将在 `child` 背后。
- 交互:`CustomPaint` 不支持手势检测。如果需要交互,可以将其包裹在 `GestureDetector` 中。
Expanded
`Expanded` 是 Flutter 中一个非常常用的 widget,通常用于在 `Row`, `Column` 或 `Flex` 组件中按比例扩展子组件的可用空间。它通过灵活地分配空间,帮助创建响应式的布局。
主要特性
- 自动填充空间:`Expanded` 使用 `flex` 属性占据父组件中未被占用的可用空间。
- 比例分配:通过 `flex` 属性,可以为多个 `Expanded` 组件指定占用空间的比例。
- 简化布局:在需要将多个子组件平分或按比例分配空间时,`Expanded` 是一个非常便利的工具。
使用示例
import 'package:flutter/material.dart';
class ExpandExampleWidget extends StatelessWidget {
const ExpandExampleWidget({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Expanded Example"),
),
body: Column(
children: <Widget>[
Container(
color: Colors.red,
height: 100,
child: const Center(
child: Text("Fixed Height"),
),
),
Expanded(
flex: 2,
child: Container(
color: Colors.green,
child: const Center(child: Text('Expanded\nFlex: 2')),
),
),
Expanded(
flex: 1,
child: Container(
color: Colors.blue,
child: const Center(child: Text('Expanded\nFlex: 1')),
),
),
],
),
);
}
}
注意事项
- 父组件限制:`Expanded` 只能用于 `Row`, `Column` 或 `Flex` 类型的父组件中,不能在其他布局(如 `Stack` 或 `ListView`)中使用。
- 可用空间:`Expanded` 只能填充父组件中未被占用的空间。因此,如果父组件没有剩余空间(比如 `Row` 中所有子组件都有固定宽度),`Expanded` 将不起作用。
- 嵌套使用:可以嵌套使用多个 `Expanded` 组件,以便在复杂布局中根据需要分配空间。
- 交替使用:与 `Flexible` 一起使用时,`Expanded` 会占用所有的可用空间,而 `Flexible` 则可以根据其子组件的大小来调整。
`Expanded` 是 Flutter 布局系统中的一个重要工具,尤其在构建响应式用户界面时非常有用。通过合理使用 `Expanded`,可以轻松实现子组件的动态布局和空间分配。它简化了构建灵活且美观。
EdgeInsets.only
`EdgeInsets.only` 是 Flutter 中用于创建具有特定边距的 `EdgeInsets` 对象的构造函数。`EdgeInsets` 是 Flutter 的布局系统中用于定义控件的内边距或外边距的一个类。通过 `EdgeInsets.only`,你可以为某个控件指定具体的边距值,只作用于指定的边。
主要特性
- 指定边距:可以为四个边(左、上、右、下)中的任意一个或多个边指定具体的边距值。
- 灵活控制:允许开发者精确控制每一边的间距,而不需要统一设置所有边的边距。
基本用法
`EdgeInsets.only` 可以在任何支持 `EdgeInsets` 的属性中使用,例如 `Padding`, `Margin`, `Container` 的 `padding` 和 `margin` 属性等。
使用案例
import 'package:flutter/material.dart';
class EdgeInsetsOnlyExamplePage extends StatelessWidget {
const EdgeInsetsOnlyExamplePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('EdgeInsets.only Example')),
body: Center(
child: Container(
color: Colors.blueAccent,
child: const Padding(
padding: EdgeInsets.only(left: 20.0, top: 10.0),
child: Text(
'Hello, Flutter!',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
);
}
}
其他相关构造函数
- `EdgeInsets.all(double value)`:为四个边设置相同的边距。
- `EdgeInsets.symmetric({double vertical, double horizontal})`:同时为水平和垂直方向设置对称的边距。
- `EdgeInsets.fromLTRB(double left, double top, double right, double bottom)`:为四个边分别设置具体的边距。
- `EdgeInsets.zero`:用于设置没有边距的 `EdgeInsets`。
使用注意事项
- 布局影响:边距的设置会影响布局,尤其是在复杂的布局中,要确保边距设置与设计需求一致。
- 嵌套使用:可以将多个 `EdgeInsets` 结合使用,以实现更复杂的布局效果。
- 响应式设计:在需要适配不同屏幕或设备时,可以结合 `MediaQuery` 动态调整 `EdgeInsets` 的值。
通过使用 `EdgeInsets.only`,开发者可以在布局中实现更精细的控制,确保每个控件的间距符合设计规范。它是 Flutter 布局系统中不可或缺的一部分,提供了灵活而强大的布局能力。
EdgeInsets.zero
`EdgeInsets.zero` 是 Flutter 中 `EdgeInsets` 类的一个常量,它表示没有边距(即上下左右的边距都为 0)。在布局中使用 `EdgeInsets.zero` 可以明确指定某个控件不需要额外的内边距或外边距。
主要用途
- 消除默认边距:在某些情况下,控件可能会有默认的边距或内边距,通过使用 `EdgeInsets.zero` 可以去除这些默认的边距。
- 明确意图:即使边距默认为 0,使用 `EdgeInsets.zero` 可以让代码更加清晰,表示边距的设计是经过有意定义的。
- 条件布局:在需要根据条件动态设置边距时,可以方便地使用 `EdgeInsets.zero` 来代表没有边距的选项。
使用案例
import 'package:flutter/material.dart';
class EdgeInsetsZeroExample extends StatelessWidget {
const EdgeInsetsZeroExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('EdgeInsets.zero Example')),
body: Center(
child: Container(
color: Colors.blueAccent,
padding: EdgeInsets.zero,
child: const Text(
'No Padding',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
),
);
}
}
其他相关用法
- 与条件语句结合:在构建动态布局时,可以通过条件语句选择使用 `EdgeInsets.zero` 或其他 `EdgeInsets` 值。
EdgeInsets padding = condition ? EdgeInsets.all(10.0) : EdgeInsets.zero;
- 默认值清除:某些 Flutter 组件可能有默认的边距设置,通过使用 `EdgeInsets.zero` 可以显式地清除这些默认设置。
注意事项
- 布局影响:使用 `EdgeInsets.zero` 会使得组件内容紧贴其父容器的边界,确保这是预期的效果。
- 代码可读性:即使某个属性的默认值为 0,使用 `EdgeInsets.zero` 可以提高代码的可读性和可维护性,明确表明设计选择。
通过使用 `EdgeInsets.zero`,开发者可以在布局中实现无边距的设计,确保控件准确地呈现设计意图。这在需要精确控制 UI 元素位置的场景中特别有用。
自定义提示弹框实现学习
import 'package:flutter/material.dart';
import 'bubble_painter.dart';
///提示弹框
class BubbleTipWidget extends StatefulWidget {
///控件高度
final double? height;
///控件宽度
final double? width;
///控件圆角
final double? radius;
///控件文本
final String text;
///需要三角形指向的x坐标
final double? x;
///需要三角形指向的y坐标
final double? y;
///三角形的位置
final ArrowLocation arrowLocation;
final VoidCallback? voidCallback;
const BubbleTipWidget(
{super.key, this.width,
this.height,
this.radius,
this.text = "",
this.arrowLocation = ArrowLocation.BOTTOM,
this.voidCallback,
this.x = 0,
this.y = 0});
@override
State<StatefulWidget> createState() => _BubbleTipWidgetState();
}
class _BubbleTipWidgetState extends State<BubbleTipWidget>
with SingleTickerProviderStateMixin {
AnimationController? progressController;
final GlobalKey paintKey = GlobalKey();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
double arrowHeight = 10;
double arrowWidth = 10;
double? x = widget.x;
double? y = widget.y;
Size size = MediaQuery.sizeOf(context);
///计算出位置的中心点
if (widget.arrowLocation == ArrowLocation.BOTTOM ||
widget.arrowLocation == ArrowLocation.TOP) {
x = widget.x! - widget.width! / 2;
} else {
y = widget.y! - widget.height! / 2;
}
///宽度是否超出
bool widthOut = (widget.width! + x!) > size.width || x < 0;
///高度是否超出
bool heightOut = (widget.height! + y!) > size.height || y < 0;
///不能小于0
if (x < 0) {
x = 0;
} else if (widthOut) {
x = size.width - widget.width!;
}
if (y < 0) {
y = 0;
} else if (heightOut) {
y = size.height - widget.height!;
}
///箭头在这个状态下是否需要居中
bool arrowCenter = (widget.arrowLocation == ArrowLocation.BOTTOM ||
widget.arrowLocation == ArrowLocation.TOP)
? !widthOut
: !heightOut;
///调整箭头状态,因为此时箭头会可能不是局中的
double arrowPosition = (widget.arrowLocation == ArrowLocation.BOTTOM ||
widget.arrowLocation == ArrowLocation.TOP)
? (widget.x! - x - arrowWidth / 2)
: (widget.y! - y - arrowHeight / 2);
///箭头的位置是按照弹出框的左边为起点计算的
if (widget.arrowLocation == ArrowLocation.BOTTOM ||
widget.arrowLocation == ArrowLocation.TOP) {
if (arrowPosition < widget.radius! + 2) {
arrowPosition = widget.radius! + 4;
} else if (arrowPosition > widget.width! - widget.radius! - 2) {
arrowPosition = widget.width! - widget.radius! - 4;
}
} else {
if (arrowPosition < widget.radius! + 2) {
arrowPosition = widget.radius! + 4;
} else if (x > widget.height! - widget.radius! - 2) {
arrowPosition = widget.height! - widget.radius! - 4;
}
}
EdgeInsets margin = EdgeInsets.zero;
if (widget.arrowLocation == ArrowLocation.TOP) {
margin = EdgeInsets.only(top: arrowHeight, right: 5, left: 5);
}
var bubbleBuild = BubbleBuilder()
..mAngle = widget.radius
..mArrowHeight = arrowHeight
..mArrowWidth = arrowWidth
..mArrowPosition = arrowPosition
..mArrowLocation = widget.arrowLocation
..arrowCenter = arrowCenter;
var alignment = Alignment.centerLeft;
if(widget.arrowLocation == ArrowLocation.TOP || widget.arrowLocation ==ArrowLocation.BOTTOM) {
alignment = Alignment.center;
}
return Scaffold(
backgroundColor: Colors.transparent,
body: GestureDetector(
///透明可以点击
behavior: HitTestBehavior.translucent,
onPanStart: _onPanStart,
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
child: Container(
alignment: Alignment.centerLeft,
width: widget.width,
height: widget.height,
margin: EdgeInsets.only(left: x, top: y),
child: Stack(
children: <Widget>[
///绘制气泡背景
CustomPaint(
key: paintKey,
size: Size(widget.width!, widget.height!),
painter: bubbleBuild.build()),
Align(
alignment: alignment,
///显示文本等
child: Container(
margin: margin,
width: widget.width,
height: widget.height! - arrowHeight,
alignment: Alignment.centerLeft,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.only(left: 20),
height: widget.height,
child: Icon(
Icons.notifications,
size: widget.height! - 30,
color: Theme.of(context).primaryColorDark,
),
),
Expanded(
child: Container(
margin: const EdgeInsets.only(left: 5, right: 5),
child: Text(
widget.text,
style: const TextStyle(fontSize: 14, color: Colors.black),
),
),
)
],
),
),
)
],
),
),
),
);
}
void _onPanStart(DragStartDetails details) {}
void _onPanUpdate(DragUpdateDetails details) {}
void _onPanEnd(DragEndDetails details) {
widget.voidCallback?.call();
}
}