Flutter 代码如何实现了一个全局悬浮按钮,当点击按钮时,会显示一个可以拖动并且通过长按可以移除的悬浮控件。
前置知识点学习
Offset
`Offset` 是 Flutter 中的一个类,用于表示二维平面中的位置或位移。它通常用于描述坐标系中的一个点,或者用于指定某种移动的向量。`Offset` 是在 Flutter 的绘制和布局过程中非常常用的类,尤其是在自定义绘制和触摸事件处理中。
构造函数
`Offset(double dx, double dy)`:
- `dx`: 在水平方向上的偏移量。
- `dy`: 在垂直方向上的偏移量。
常用属性
- `dx`: 水平方向的偏移量。
- `dy`: 垂直方向的偏移量。
常用方法
- `distance`: 返回从原点(0, 0)到这个偏移量的直线距离。
- `direction`: 返回从原点到这个偏移量的角度(以弧度为单位),相对于水平方向。
- `scale(double scaleX, double scaleY)`: 返回一个新的 `Offset`,其 `dx` 和 `dy` 分别乘以 `scaleX` 和 `scaleY`。
- `translate(double translateX, double translateY)`: 返回一个新的 `Offset`,其 `dx` 和 `dy` 加上 `translateX` 和 `translateY`。
操作符重载
- 加法 (`+`): 可以将两个 `Offset` 相加,返回新的 `Offset`。
- 减法 (`-`): 可以将两个 `Offset` 相减,返回新的 `Offset`。
- 乘法 (`*`): 可以将 `Offset` 与一个标量相乘,返回新的 `Offset`。
- 除法 (`/`): 可以将 `Offset` 与一个标量相除,返回新的 `Offset`。
示例
下面是一个简单的示例,展示如何使用 `Offset`:
Dart
import 'package:flutter/material.dart';
class OffserExample extends StatelessWidget {
const OffserExample({super.key});
@override
Widget build(BuildContext context) {
Offset offset1 = const Offset(10.0, 20.0);
Offset offset2 = const Offset(5.0, 15.0);
Offset result = offset1 + offset2; // Add two offsets
double distance = result.distance; // Calculate distance from origin
return Scaffold(
appBar: AppBar(
title: const Text('Offset Example'),
),
body: Center(
child: Text(
'Resulting Offset: $result\nDistance from origin: $distance',
textAlign: TextAlign.center,
),
),
);
}
}
解释
- 在这个示例中,`offset1` 和 `offset2` 是两个 `Offset` 实例。
- 通过 `+` 操作符,我们将两个 `Offset` 相加,得到一个新的 `Offset`。
- `distance` 属性计算从原点到 `result` 的直线距离。
使用场景
- 自定义绘制: 在 Flutter 的 `CustomPainter` 中,`Offset` 常用于指定绘制起点。
- 触摸事件: 在处理触摸事件时,`Offset` 用于描述触摸点的位置。
- 布局计算: 在自定义布局逻辑中,`Offset` 可以用于计算组件的位置和移动。
Overlay
`Overlay` 是 Flutter 中的一个强大组件,用于在应用的普通界面层之上创建浮动层。它允许开发者在现有界面之上展示额外的内容,比如弹出窗口、工具提示、悬浮按钮等。这种层叠效果是通过 `OverlayEntry` 来实现的。
主要特性
- 层叠显示: `Overlay` 可以将多个 `OverlayEntry` 叠加在一起,形成一个层次结构,最上面的条目会显示在最上层。
- 动态插入和移除: 可以在运行时动态添加或移除 `OverlayEntry`,从而实现动态显示和隐藏界面元素。
- 灵活性: 可以在任何地方插入 `OverlayEntry`,不受父组件的限制,这给予了开发者极大的灵活性。
核心组件
1.`Overlay`:
- `Overlay` 是一个用于管理和显示 `OverlayEntry` 的容器。通常在 `MaterialApp` 的根布局中已经存在一个 `Overlay`。
2.`OverlayEntry`:
- `OverlayEntry` 是一个可以插入到 `Overlay` 中的条目。每个条目都可以独立控制其显示和隐藏。
使用步骤
1.获取 `Overlay` 的引用:
- 使用 `Overlay.of(context)` 来获得当前上下文的 `Overlay` 实例。
2.创建 `OverlayEntry`:
- 定义一个 `OverlayEntry`,提供一个 `builder` 方法来返回要显示的 widget。
3.插入 `OverlayEntry`:
- 使用 `Overlay.insert(overlayEntry)` 方法,将 `OverlayEntry` 插入到 `Overlay` 中。
4.移除 `OverlayEntry`:
- 调用 `overlayEntry.remove()` 方法来从 `Overlay` 中移除条目。
示例
以下是一个简单的示例,展示如何使用 `Overlay` 来显示一个悬浮的红色圆形按钮:
Dart
import 'package:flutter/material.dart';
class OverlayExample extends StatefulWidget {
const OverlayExample({super.key});
@override
_OverlayExampleState createState() {
return _OverlayExampleState();
}
}
class _OverlayExampleState extends State<OverlayExample> {
OverlayEntry? _overlayEntry;
void _showOverlay(BuildContext context) {
OverlayState overlayState = Overlay.of(context);
_overlayEntry = OverlayEntry(
builder: (context) => Positioned(
top: 100,
left: 100,
child: GestureDetector(
onTap: () {
_overlayEntry?.remove();
},
child: const CircleAvatar(
radius: 50,
backgroundColor: Colors.redAccent,
child: Icon(
Icons.close,
color: Colors.white,
),
),
)));
overlayState.insert(_overlayEntry!);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Overlay Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () => _showOverlay(context),
child: const Text('Show Overlay'),
),
),
);
}
}
解释
`OverlayEntry`:
- 创建了一个 `OverlayEntry`,它包含一个 `Positioned` widget,用于定位其位置。
- 包含一个 `GestureDetector`,用来监听点击事件,点击后移除该 `OverlayEntry`。
展示和移除:
- 点击按钮调用 `_showOverlay` 方法,将 `OverlayEntry` 插入到 `Overlay` 中。
- `GestureDetector` 侦听点击事件。当用户点击红色圆形按钮时,调用 `_overlayEntry?.remove()` 将其从 `Overlay` 中移除。
使用场景
- 浮动按钮: 实现全局悬浮按钮,用户可以在任何页面上访问。
- 弹出窗口: 创建自定义的弹出窗口或对话框,而不需要中断当前页面的布局。
- 工具提示: 在特定的 UI 元素上方显示工具提示或帮助信息。
- 通知: 实现临时通知或警告,显示在应用的顶部或底部。
注意事项
- 层叠顺序: `OverlayEntry` 是按插入顺序堆叠的,后插入的条目会显示在前面的条目之上。
- 管理条目: 在使用 `OverlayEntry` 时,确保正确管理其生命周期,避免内存泄漏。例如,及时调用 `remove()` 方法移除不再需要的条目。
- 性能考虑: 虽然 `Overlay` 提供了灵活的 UI 设计,但在频繁更新或大量使用时,可能会影响性能。因此,合理使用 `Overlay` 以保持应用的流畅性。
通过 `Overlay` 和 `OverlayEntry`,Flutter 提供了强大的工具来创建灵活的 UI 组件,允许在不干扰现有布局的情况下实现复杂的用户交互。
GestureDetector
`GestureDetector` 是 Flutter 中用于检测手势的一个小部件。它可以包裹其他小部件,并通过监听用户的手势来响应触摸事件。`GestureDetector` 提供了多种手势回调函数,允许开发者处理点击、双击、长按、拖动、缩放等手势。
主要属性
`GestureDetector` 提供了多种手势检测回调,以下是一些常用的属性:
- `onTap`: 当用户轻触屏幕时触发。
- `onDoubleTap`: 当用户快速连续点击两次时触发。
- `onLongPress`: 当用户长时间按住屏幕时触发。
- `onPanUpdate`: 当用户在屏幕上拖动时触发,每次拖动位置变化时都会调用。
- `onPanStart`: 当用户开始拖动时触发。
- `onPanEnd`: 当用户结束拖动时触发。
- `onScaleUpdate`: 当用户执行缩放手势时触发。
- `onVerticalDragUpdate`: 当用户在垂直方向上拖动时触发。
- `onHorizontalDragUpdate`: 当用户在水平方向上拖动时触发。
使用示例
以下是一个简单的示例,展示如何使用 `GestureDetector` 处理点击和拖动手势:
Dart
import 'package:flutter/material.dart';
class GestureDetectorExample2 extends StatefulWidget {
const GestureDetectorExample2({super.key});
@override
_GestureDetectorExampleState2 createState() {
return _GestureDetectorExampleState2();
}
}
class _GestureDetectorExampleState2 extends State<GestureDetectorExample2> {
Color _color = Colors.blueGrey;
Offset _offset = const Offset(100, 100);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('GestureDetector Example'),
),
body: Stack(
children: [
Positioned(
left: _offset.dx,
top: _offset.dy,
child: GestureDetector(
onTap: () {
setState(() {
_color = _color == Colors.blue ? Colors.red : Colors.blue;
});
},
onPanUpdate: (details) {
setState(() {
_offset += details.delta;
});
},
child: Container(
width: 100,
height: 100,
color: _color,
child: const Center(
child: Text(
'Tap or Drag',
style: TextStyle(color: Colors.white),
),
),
),
))
],
),
);
}
}
解释
- `GestureDetector`: 包裹了一个 `Container`,用于检测点击和拖动手势。
- `onTap` 回调: 每次点击 `Container` 时,切换颜色。
- `onPanUpdate` 回调: 每次拖动时更新 `_offset`,从而移动 `Container` 的位置。
- `Positioned`: 用于根据 `_offset` 的值定位 `Container`,从而实现拖动效果。
使用场景
- 交互元素: 为按钮、图标或任何可交互元素添加手势响应。
- 自定义手势: 在自定义组件中实现复杂的手势交互,如拖放、缩放等。
- 游戏开发: 在游戏中检测用户的触摸和拖动以实现交互。
Material
`Material` 是 Flutter 提供的一个核心组件,它实现了 Material Design 的视觉效果和行为规范。`Material` 小部件是许多 Flutter UI 组件的基础,比如按钮、卡片、对话框等,这些组件都依赖于 `Material` 来提供背景、阴影、剪裁等效果。
主要功能
- 背景颜色: `Material` 提供了背景颜色,可以是单一颜色或者是渐变。
- 阴影: 通过 `elevation` 属性,`Material` 可以在其下方投射阴影,营造出悬浮的效果。
- 剪裁: 通过 `shape` 属性,可以将 `Material` 剪裁成特定的形状,如圆形、矩形、圆角矩形等。
- 触觉反馈: 在交互时,`Material` 提供了触觉反馈,比如波纹效果(`InkWell`)。
主要属性
- `color`: 设置 `Material` 的背景颜色。
- `elevation`: 控制阴影的深度,值越大阴影越明显。
- `shape`: 定义 `Material` 的形状,可以是 `RoundedRectangleBorder`、`CircleBorder` 等。
- `borderRadius`: 如果形状是矩形,可以通过这个属性设置圆角。
- `type`: 指定 `Material` 的类型,`MaterialType.canvas` 表示没有阴影的背景,`MaterialType.card` 表示有阴影的卡片。
示例
以下是一个简单的示例,展示如何使用 `Material`:
Dart
import 'package:flutter/material.dart';
class MaterialExample extends StatelessWidget {
const MaterialExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Material Example'),
),
body: Center(
child: Material(
color: Colors.blueGrey,
elevation: 5.0,
borderRadius: BorderRadius.circular(10.0),
child: Container(
width: 200,
height: 100,
alignment: Alignment.center,
child: const Text(
'Hello, Material!',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
),
),
);
}
}
解释
- `Material`: 包裹了一个 `Container`,提供背景颜色、阴影和圆角效果。
- `elevation`: 设置为 5.0,创建了一个轻微的阴影效果。
- `borderRadius`: 通过 `BorderRadius.circular()` 设置圆角,从而使 `Material` 的背景具有圆角效果。
- `color`: 设置为蓝色,使得 `Material` 的背景为蓝色。
使用场景
- 卡片和面板: `Material` 常用于实现卡片、面板等带有阴影和圆角的组件。
- 自定义按钮: 通过组合 `Material` 和 `InkWell`,可以创建自定义的按钮效果。
- 对话框和弹出窗口: `Material` 提供了基础的视觉效果,是实现对话框、弹出窗口的基础。
注意事项
- 性能: `elevation` 属性会增加阴影计算,因此在性能敏感的环境中应谨慎使用高阴影值。
- 触摸效果: 需要与 `InkWell` 或 `InkResponse` 结合使用才能实现点击时的波纹效果。
BoxDecoration
`BoxDecoration` 是 Flutter 中用于装饰容器(如 `Container`)的一个类。它允许开发者为容器添加背景颜色、图像、边框、阴影和渐变等多种视觉效果。`BoxDecoration` 是一个强大的工具,能够帮助你实现复杂的 UI 样式。
主要属性
`BoxDecoration` 提供了多种属性以定义容器的装饰效果:
- `color`: 背景颜色。设置容器的背景色。
- `image`: 背景图像。通过 `DecorationImage` 设置背景图像及其位置、重复方式等。
- `border`: 边框。通过 `Border` 类设置容器的边框,可以指定每边的宽度和颜色。
- `borderRadius`: 圆角边框半径。常用于给矩形容器添加圆角。
- `boxShadow`: 阴影效果。通过 `BoxShadow` 类定义阴影的颜色、偏移量、模糊半径和扩展半径。
- `gradient`: 渐变效果。可以是线性渐变(`LinearGradient`)或径向渐变(`RadialGradient`)。
- `shape`: 形状。可以是 `BoxShape.rectangle`(默认)或 `BoxShape.circle`。
使用示例
以下是一个简单的示例,展示如何使用 `BoxDecoration` 来装饰一个 `Container`:
Dart
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: Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.black, width: 3),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
offset: const Offset(2, 2),
blurRadius: 5)
],
gradient: const LinearGradient(
colors: [Colors.blue, Colors.lightBlueAccent],
begin: Alignment.topLeft,
end: Alignment.bottomRight),
),
child: const Text(
'Hello, BoxDecoration!',
style: TextStyle(color: Colors.white, fontSize: 16),
textAlign: TextAlign.center,
),
),
),
);
}
}
解释
- `Container`: 使用 `BoxDecoration` 来装饰。
- `color`: 设置容器的背景颜色为蓝色。
- `borderRadius`: 通过 `BorderRadius.circular(20)` 实现圆角效果。
- `border`: 添加一个黑色边框,宽度为 3。
- `boxShadow`: 添加一个半透明的黑色阴影,偏移量为 (2, 2),模糊半径为 5。
- `gradient`: 使用线性渐变从蓝色到浅蓝色。
使用场景
- 卡片样式: 为 UI 卡片提供复杂的视觉效果,包括阴影、圆角和渐变。
- 背景装饰: 为布局元素添加背景图像、颜色和渐变。
TextButton
`TextButton` 是 Flutter 中的一种按钮组件,是 Material Design 提供的按钮类型之一。它是一种扁平的按钮,通常用于没有明显边框或阴影的场合。`TextButton` 是 `FlatButton` 的替代品,`FlatButton` 在 Flutter 的较早版本中被使用,但在现代 Flutter 中被 `TextButton` 所取代。
主要属性
- `onPressed`: 一个回调函数,当按钮被点击时调用。如果此属性为 `null`,按钮将被禁用,通常会变灰。
- `child`: 按钮的内容,通常是一个 `Text` 小部件,但也可以是其他小部件,如 `Icon` 或 `Row`。
- `style`: 用于自定义按钮的外观,包括文本样式、按钮背景颜色、形状、边距等。
使用示例
以下是如何使用 `TextButton` 的简单示例:
Dart
import 'package:flutter/material.dart';
class TextButtonExample22 extends StatelessWidget {
const TextButtonExample22({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TextButton Example'),
),
body: Center(
child: TextButton(
onPressed: () {
print('TextButton pressed!');
},
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.blue,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text('Click'),
),
),
);
}
}
解释
- `onPressed`: 这是一个必需的属性,用于指定按钮按下时的行为。在示例中,它简单地打印了一条消息。
- `style`: 使用 `TextButton.styleFrom` 来定义按钮的样式。
- `primary`: 文本颜色。
- `backgroundColor`: 按钮背景颜色。
- `padding`: 定义按钮内边距。
- `shape`: 定义按钮的形状,这里使用 `RoundedRectangleBorder` 来创建圆角矩形。
- `child`: 按钮的内容,这里是一个 `Text` 小部件,显示按钮文本。
使用场景
- 表单提交: 用于表单中的提交按钮等。
- 导航: 实现简单的页面导航或动作触发。
- 扁平风格设计: 在需要满足 Material Design 扁平设计风格的应用中使用。
其他注意事项
- 禁用状态: 如果 `onPressed` 设置为 `null`,按钮将被禁用,可以通过样式调整按钮在禁用状态下的外观。
- 替代品: 与 `ElevatedButton` 和 `OutlinedButton` 一起,`TextButton` 是 Flutter 提供的三种主要按钮之一。它适用于不需要突出显示的按钮场景。
通过使用 `TextButton`,你可以轻松创建符合 Material Design 的现代、简洁的按钮。其灵活的样式系统允许你根据特定的设计需求自定义按钮的外观和行为。
flutter实现全局悬浮按钮代码学习
Dart
import 'package:flutter/material.dart';
class FloatingTouchDemoPage22 extends StatefulWidget {
const FloatingTouchDemoPage22({super.key});
@override
_FloatingTouchDemoPageState22 createState() {
return _FloatingTouchDemoPageState22();
}
}
class _FloatingTouchDemoPageState22 extends State<FloatingTouchDemoPage22> {
Offset offset = const Offset(200, 200);
final double height = 80;
_showFloating() {
var overlayState = Overlay.of(context);
OverlayEntry? overlayEntry;
overlayEntry = OverlayEntry(builder: (context) {
return Stack(
children: <Widget>[
Positioned(
left: offset.dx,
top: offset.dy,
child: _buildFloating(overlayEntry),
)
],
);
});
overlayState.insert(overlayEntry);
}
_buildFloating(OverlayEntry? overlayEntry) {
return GestureDetector(
behavior: HitTestBehavior.deferToChild,
onPanDown: (details) {
offset = details.globalPosition - Offset(height / 2, height / 2);
overlayEntry!.markNeedsBuild();
},
onPanUpdate: (DragUpdateDetails details) {
offset = offset + details.delta;
overlayEntry!.markNeedsBuild();
},
onLongPress: () {
overlayEntry!.remove();
},
child: Material(
color: Colors.transparent,
child: Container(
height: height,
width: height,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.redAccent,
borderRadius: BorderRadius.all(Radius.circular(height / 2))),
child: const Text(
"长按\n移除",
style: TextStyle(color: Colors.white),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("FloatingTouchDemoPage"),
),
body: Center(
child: TextButton(
onPressed: () {
_showFloating();
},
child: const Text("show floating button"),
),
),
);
}
}