flutter 使用Overlay + GestureDetector 制作一个全局可拖拽悬浮按钮

使用Overlay + GestureDetector 制作一个全局可拖拽悬浮按钮

1.场景

现在需要做一个Test按钮,悬浮在所有页面之上,并且可以拖拽。

2.思路

1)悬浮按钮可以使用flutter提供的Overlay + OverlayEntry 组合实现

2)拖拽功能可以使用GestureDetector手势按钮或者Draggable实现(PS:我做了一版Draggable实现的,但是发现它会有原本的widget浮在原地,显然不是我要的效果)

3)点击的时候我是让它弹出一个底部弹框,这里你们可以*发挥,本篇文章不做多余赘述

3.具体实现

app.dart/main.dart(应用程序入口)

@override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: PubScaffold(
        child: OKToast(
          child: MaterialApp(
            title: 'Flutter Demo',
            theme: ThemeData(
              primarySwatch: Colors.green,
            ),
            ...
          ),
        ),
      ),
    );
  }

这里的PubScaffold就是我封装的一个悬浮按钮组件,把它包裹在MaterialApp外面,就可以实现悬浮在所有的组件之上的一个按钮啦(当然也可以不是按钮,具体样式可以自己定义)。下面我们来看一下PubScaffold中的代码吧~

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:printer/foundation/method_channel/poooli_navigator.dart';

class PubScaffold extends StatefulWidget {
  final Widget child;
  PubScaffold({this.child});

  @override
  _PubScaffoldState createState() => _PubScaffoldState();
}

class _PubScaffoldState extends State<PubScaffold> {
  bool draggable = false;

  //静止状态下的offset
  Offset idleOffset = Offset(0, 0);
  //本次移动的offset
  Offset moveOffset = Offset(0, 0);
  //最后一次down事件的offset
  Offset lastStartOffset = Offset(0, 0);

  int count = 0;

  final List<String> testWidgetList = [
    '测试1',
    '测试2',
  ];

  testAppFun(e) {
  	// TODO: 你的代码逻辑
  }

  // 显示一个底部弹窗,这里是一个测试列表
  showTestList() {
    showModalBottomSheet(
      context: context,
      enableDrag: false,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(20.0),
          topRight: Radius.circular(20.0),
        ),
      ),
      builder: (BuildContext context) {
        return ListView(
          children: testWidgetList
              .map(
                (e) => Container(
                  decoration: BoxDecoration(
                    border: Border(
                      bottom: BorderSide(color: Color(0xFFe3e3e3)),
                    ),
                  ),
                  child: ListTile(
                    onTap: () => testAppFun(e),
                    title: Text(e),
                  ),
                ),
              )
              .toList(),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        // 显示悬浮按钮
        WidgetsBinding.instance
              .addPostFrameCallback((_) => _insertOverlay(context));
        return widget.child;
      },
    );
  }

 // 悬浮按钮,可以拖拽(可自定义样式)
  void _insertOverlay(BuildContext context) {
    return Overlay.of(context).insert(
      OverlayEntry(builder: (context) {
        final size = MediaQuery.of(context).size;
        print(size.width);
        return Positioned(
          top: draggable ? moveOffset.dy : size.height - 102,
          left: draggable ? moveOffset.dx : size.width - 72,
          child: GestureDetector(
           // 移动开始
            onPanStart: (DragStartDetails details) {
              setState(() {
                lastStartOffset = details.globalPosition;
                draggable = true;
              });
              if (count <= 1) {
                count++;
              }
            },
            // 移动中
            onPanUpdate: (DragUpdateDetails details) {
              setState(() {
                moveOffset =
                    details.globalPosition - lastStartOffset + idleOffset;
                if (count > 1) {
                  moveOffset = Offset(max(0, moveOffset.dx), moveOffset.dy);
                } else {
                  moveOffset = Offset(max(0, moveOffset.dx + (size.width - 72)),
                      moveOffset.dy + (size.height - 102));
                }
              });
            },
            // 移动结束
            onPanEnd: (DragEndDetails detail) {
              setState(() {
                idleOffset = moveOffset * 1;
              });
            },
            child: TestContainer(
              onPress: () => showTestList(),
            ),
          ),
        );
      }),
    );
  }
}

// 悬浮按钮的样式
class TestContainer extends StatelessWidget {
  final Function onPress;
  TestContainer({this.onPress});
  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.transparent,
      child: GestureDetector(
        onTap: onPress,
        child: Container(
          width: 56,
          height: 56,
          alignment: Alignment.center,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: Colors.green[600],
          ),
          child: Text(
            "Test",
            style: TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ),
    );
  }
}

1.全局悬浮按钮

这里我们用的是flutter自带的material库中的Overlay组件,具体使用方法如下:

void _insertOverlay(BuildContext context) {
    return Overlay.of(context).insert(
      OverlayEntry(builder: (context) {
        final size = MediaQuery.of(context).size;
        print(size.width);
        return Positioned(
          width: 56,
          height: 56,
          top: size.height - 72,
          left: size.width - 72,
          child: Material(
            color: Colors.transparent,
            child: GestureDetector(
              onTap: () => print('ON TAP OVERLAY!'),
              child: Container(
                decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.redAccent),
              ),
            ),
          ),
        );
      }),
    );
  }

使用Overlay.of(context).insertOverlayEntry自定义构建一个悬浮组件,当你想要显示它的时候只需在builder中插入以下代码

WidgetsBinding.instance.addPostFrameCallback((_) => _insertOverlay(context));

参考:https://*.com/questions/57069641/how-to-overlay-a-widget-on-top-of-a-flutter-app

2.拖拽

使用GestureDetector手势组件实现。具体实现参考代码

上一篇:使用 ffmpeg 帮助制作 ppt 视频


下一篇:SAR成像专栏目录