Flutter实现气泡提示框学习
前置知识点学习
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>();@overrideWidget 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";@overrideWidget 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(); // 这里不能使用 constconst MyWidget({Key? key}) : super(key: key); // 错误的用法
}
可以通过以下方式修正:
class MyWidget extends StatelessWidget {final GlobalKey _key = GlobalKey(); // 不使用 constMyWidget({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 {@overrideWidget 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;@overrideWidget build(BuildContext context) {return Container(child: Center(child: FadeTransition(opacity: _controller,child: const FlutterLogo(size: 100.0),),),);}@overridevoid initState() {super.initState();//vsync: this => 需要一个 TickerProvider,这里就是 SingleTickerProviderStateMixin 提供的_controller =AnimationController(vsync: this, duration: const Duration(seconds: 2));_controller.repeat(reverse: true);}@overridevoid 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});@overrideWidget 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});@overrideWidget 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});@overrideWidget 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});@overrideWidget 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;@overrideWidget 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});@overrideWidget 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;@overrideWidget 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 {@overridevoid 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);}@overridebool 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});@overrideWidget 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});@overrideWidget 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});@overrideWidget 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});@overrideWidget 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});@overrideState<StatefulWidget> createState() => _BubbleTipWidgetState();
}class _BubbleTipWidgetState extends State<BubbleTipWidget>with SingleTickerProviderStateMixin {AnimationController? progressController;final GlobalKey paintKey = GlobalKey();@overridevoid initState() {super.initState();}@overrideWidget 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;///不能小于0if (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();}
}
相关文章:
Flutter实现气泡提示框学习
前置知识点学习 GlobalKey GlobalKey 是 Flutter 中一个非常重要的概念,它用于唯一标识 widget 树中的特定 widget,并提供对该 widget 的访问。这在需要跨越 widget 树边界进行交互或在 widget 树重建时保持状态时尤其有用。 GlobalKey 的作用 唯一标…...
vue3 路由守卫
在Vue 3中,路由守卫是一种控制和管理路由跳转的机制。它允许你在执行导航前后进行一些逻辑处理,比如权限验证、数据预取等,从而增强应用的安全性和效率。路由守卫分为几种不同的类型,每种类型的守卫都有其特定的应用场景。 其实路…...
【MATLAB源码-第218期】基于matlab的北方苍鹰优化算法(NGO)无人机三维路径规划,输出做短路径图和适应度曲线.
操作环境: MATLAB 2022a 1、算法描述 北方苍鹰优化算法(Northern Goshawk Optimization,简称NGO)是一种新兴的智能优化算法,灵感来源于北方苍鹰的捕猎行为。北方苍鹰是一种敏捷且高效的猛禽,广泛分布于北…...
如何控制自己玩手机的时间?两台苹果手机帮助自律
对一些人来说,被智能手机“绑架”是一件心甘情愿的事,和它相处的一天中,不必面对现实的压力,它就像个“舒适区”。这是因为在使用手机的过程中,应用程序(尤其是游戏和社交媒体应用)会不断刺激大…...
【java-Neo4j 5开发入门篇】-最新Java开发Neo4j
系列文章目录 前言 上一篇文章讲解了Neo4j的基本使用,本篇文章对Java操作Neo4j进行入门级别的阐述,方便读者快速上手对Neo4j的开发。 一、开发环境与代码 1.docker 部署Neo4j #这里使用docker部署Neo4j,需要镜像加速的需要自行配置 docker run --name…...
Python的3D可视化库 - vedo (1)简介和模块功能概览
文章目录 1. vedo和它支持的功能简介1.1 安装vedo1.2 命令行接口1.3 导出3D文件1.4 文件格式转换 2. vedo模块功能概览2.1 绘制和渲染visual 管理可视化、对象及其属性的显示的基类plotter 3D渲染colors 定义和显示颜色dolfin FEniCS/Dolfin库的支持 2.2 图形数据管理mesh 多边…...
全面解析:HTML页面的加载全过程(一)--输入URL地址,与服务器建立连接
用户输入URL地址,与服务器建立连接 用户在浏览器地址栏输入一个URL 浏览器开始执行以下三步操作操作:url解析、DNS查询、TCP连接 第一步:URL解析 什么是URL? URL(Uniform Resource Locator,统一资源定位符)是互联网…...
elasticsearch的倒排索引是什么?
大家好,我是锋哥。今天分享关于【elasticsearch的倒排索引是什么?】面试题。希望对大家有帮助; elasticsearch的倒排索引是什么? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 倒排索引(Inverted Index&a…...
Ubuntu VNC Session启动chromium和firefox报错
问题描述 VNC客户端连接到Ubuntu Server后,启动chromium和firefox时报错: $ chromium [348564:348564:1117/102143.085649:ERROR:ozone_platform_x11.cc(244)] Missing X server or $DISPLAY [348564:348564:1117/102143.085732:ERROR:env.cc(258)] Th…...
【Tealscale + Headscale + 自建服务器】异地组网笔记
文章目录 效果为什么要用 Headscale云服务器安装 Headscale配置 config.yaml创建反向代理搭建管理 UI授权管理 UI添加互联设备参考 效果 首先是连接情况,双端都连接上自建的 Headscale, 手机使用移动流量,测试一下 ping 值 再试试进入游戏 可…...
C++ 编程基础(8)模版 | 8.2、函数模版
文章目录 一、函数模版1、声明与定义2、模版参数3、模板的实例化3.1、隐式实例化3.2、显示实例化 4、模版的特化5、注意事项6、总结 前言: C 函数模板是一种强大的特性,它允许程序员编写与类型无关的代码。通过使用模板,函数或类可以处理不同…...
Android Studio音频视频播放器课程设计
这个项目适合刚刚学习Android studio的初学者,实现音视频的基本播放功能,各项功能的页面都做的比较简单,特别适用于初学者,其特点在于本项目抛开了各种花里胡哨的制作,以最接近初学者的样式画面呈现,完全不…...
速盾:CDN是否支持屏蔽IP?
CDN(内容分发网络)是一种用于提高网站性能和可靠性的技术,通过将内容分发到距离终端用户更近的节点,减少了数据传输的延迟并提高了用户体验。在CDN中,屏蔽IP是一项重要的功能,可以帮助网站屏蔽无效或恶意请…...
机器学习—学习曲线
学习曲线是帮助理解学习算法如何工作的一种方法,作为它所拥有的经验的函数。 绘制一个符合二阶模型的学习曲线,多项式或二次函数,画出交叉验证错误Jcv,以及Jtrain训练错误,所以在这个曲线中,横轴将是Mtrai…...
在 macOS 和 Linux 中,波浪号 `~`的区别
文章目录 1、在 macOS 和 Linux 中,波浪号 ~macOS示例 Linux示例 区别总结其他注意事项示例macOSLinux 结论 2、root 用户的主目录通常是 /root解释示例切换用户使用 su 命令使用 sudo 命令 验证当前用户总结 1、在 macOS 和 Linux 中,波浪号 ~ 在 macO…...
【Java】实战:多数元素
一、题目描述 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入:nums [3,2,3] 输出&#x…...
一文解决Latex中的eps报错eps-converted-to.pdf not found: using draft setting.
在使用Vscode配的PDFLatex编译IEEE TII的Latex模板时,出现eps文件不能转换为pdf错误,看了几十篇方法都没用,自己研究了半天终于可以正常运行了。主要原因还是Settings.JSON中的PDFLatex模块缺少:"--shell-escape", 命令…...
计算光纤色散带来的相位移动 matlab
需要注意的地方 1.以下内容纯属个人理解,很有可能不准确,请大家仅做参考 2.光速不要直接用3e8 m/s,需要用精确的2.9979.... 3.光的频率无论在真空还是光纤(介质)都是不变的,是固有属性,但是波长lambdac/f在不同的介…...
国内docker pull拉取镜像的解决方法
访问网站,查找该网站上可用的镜像源,然后替换掉下面代码中的hub-mirror.c.163.com: docker pull hub-mirror.c.163.com/library/nginx:latest 另外,进入到镜像之后,可以使用下面的命令查看操作系统版本。 lsb_releas…...
“Kafka面试攻略:核心问题与高效回答”
1,生产者发送消息的原理 发送消息的过程中,涉及到两个线程,main线程和sender线程,main线程会创建一个双端队列,main线程向双端队列发送消息,sender线程从双端队列里拉取消息,发送给Kafka Broke…...
C++ 多线程std::thread以及条件变量和互斥量的使用
前言 本文章主要介绍C11语法中std::thread的使用,以及条件变量和互斥量的使用。 std::thread介绍 构造函数 std::thread 有4个构造函数 // 默认构造函,构造一个线程对象,在这个线程中不执行任何处理动作 thread() noexcept;// 移动构造函…...
新华三H3CNE网络工程师认证—子接口技术
子接口(subinterface)是通过协议和技术将一个物理接口(interface)虚拟出来的多个逻辑接口。在VLAN虚拟局域网中,通常是一个物理接口对应一个 VLAN。在多个 VLAN 的网络上,无法使用单台路由器的一个物理接口…...
【MySQL】InnoDB内存结构
目录 InnoDB内存结构 主要组成 缓冲池 缓冲池的作用 缓冲池的结构 缓冲池中页与页之间连接方式分析 缓冲池如何组织数据 控制块初始化 页面初始化 缓冲池中页的管理 缓冲区淘汰策略 查看缓冲池信息 总结 变更缓冲区-Chang Buffer 变更缓冲区的作用 主要配置选项…...
基于大数据爬虫数据挖掘技术+Python的网络用户购物行为分析与可视化平台(源码+论文+PPT+部署文档教程等)
#1024程序员节|征文# 博主介绍:CSDN毕设辅导第一人、全网粉丝50W,csdn特邀作者、博客专家、腾讯云社区合作讲师、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老…...
蓝桥杯每日真题 - 第19天
题目:(费用报销) 题目描述(13届 C&C B组F题) 解题思路: 1. 问题抽象 本问题可以看作一个限制条件较多的优化问题,核心是如何在金额和时间约束下选择最优方案: 动态规划是理想…...
CentOS7.9.2009的yum更换vault地窖保险库过期源,epel的archive归档源 笔记241117
CentOS7.9.2009的yum更换vault地窖保险库过期源,epel的archive归档源 笔记241117 备份 /etc/yum.repos.d 文件夹 tempUri/etc/yum.repos.d ; sudo cp -a $tempUri $tempUri.$(date %0y%0m%0d%0H%0M%0Sns%0N).bak清空 /etc/yum.repos.d 文件夹 sudo rm -rf /etc…...
Spark SQL大数据分析快速上手-完全分布模式安装
【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 Hadoop完全分布式环境搭建步骤-CSDN博客,前置环境安装参看此博文 完全分布模式也叫集群模式。将Spark目…...
Java面试题2024-Java基础
Java基础 1、 Java语言有哪些特点 1、简单易学、有丰富的类库 2、面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高) 3、与平台无关性(JVM是Java跨平台使用的根本) 4、可靠安全 5、支持多线程 2、…...
局域网协同办公软件,2024安全的协同办公软件推荐
在2024年,随着数字化转型的深入和远程工作需求的增加,协同办公软件已成为企业提升工作效率、优化沟通流程的重要工具。 以下是一些值得推荐的安全的协同办公软件: 钉钉 功能全面:钉钉是一款综合性极强的企业级协同软件ÿ…...
osg、osgearth简介及学习环境准备
一、osg简介(三维场景图渲染与调度引擎) OSG是Open Scene Graphic 的缩写,OSG于1997年诞生于以为滑翔机爱好者之手,Don burns 为了对滑翔机的飞行进行模拟,对openGL的库进行了封装,osg的雏形就这样诞生了&…...
柳北网站制作/网站功能优化
Microsoft Visual Studio 2010 的项目为件改为Microsoft Visual Studio 2015默认打开 2010 的Solution (.Sln) file Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 --默认打开的版本IDE Geovin Du 涂聚文 注释 Project("{FAE04EC…...
生产企业做网站有用吗/百度搜索优化怎么做
遇到这种情况,现有项目的数据库已经建好,数据表也已经创建完成。问题来的,数据库不能插入中文,调试时候发现中文数据从发送请求到最后请求处理完成这些步骤,中文还没有发生乱码。只有在存储到数据库后查询数据并显示才…...
太原做网站 小程序/seo实战密码电子版
2019独角兽企业重金招聘Python工程师标准>>> 可以打包各种格式 http://www.freefontconverter.com/ 转载于:https://my.oschina.net/linsk1998/blog/2874297...
湘潭公司做网站/创建网站的公司
前言 由于本人参加面试,但是JVM这块回答的十分不好,问了面试官,面试官说我基础不行!我真的不行,所以看过的不一定能理解,感觉之前就是糊弄任务,然后这次等实训结束,啥都干完了&#…...
有哪些做软件的网站有哪些/市场营销毕业后做什么工作
本系列探寻AngularJS的路由机制,在WebStorm下开发。 AngularJS路由系列包括:1、AngularJS路由系列(1)--基本路由配置2、AngularJS路由系列(2)--刷新、查看路由,路由事件和URL格式,获取路由参数,路由的Resolve3、AngularJS路由系列…...
wordpress问答社区/seo站内优化站外优化
自己的经验总结,有错的话请留言,第一时间更改。 先大概说一下IOSAPP上架的几个步骤(详细步骤见下图): 创建证书请求文件 登录苹果开发者中心生成发布者证书(下载下来要双击一下) 设置APPID&…...