Flutter-Dart全局可拖动悬浮球

2023-12-11 20:41:07 浏览数 (1)

当我们全局都需要用到某个设定且随时需要根据需求改变时,那么全局悬浮球是一个最好的选择(可拖动),参考其他大佬的文章,优化封装了一个简易的悬浮球,记录一下0.0。

Dart全局悬浮球

代码语言:javascript复制
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

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

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

class _PubScaffoldState extends State<PubScaffold> {
  List _bottomSheetList = ['x','y','z'];
  bool dragAble = false;
  // bottomSheet是否已经显示
  bool isShow = false;

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

  int count = 0;
  static OverlayEntry entry;

  /// 列表点击事件
  selectItemCallBack(e) {
    print('选中${e}');
    if (isShow) {
      Navigator.pop(context);
    }
  }

  /// 显示一个底部弹窗,这里是一个测试列表。
  showSelectList() async {
    KeyboardBack.keyboardBack();
    if (isShow) {
      Navigator.pop(context);
      return;
    }
    var flag = await showModalBottomSheet(
      isScrollControlled: true,
      context: context,
      enableDrag: false,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(10.0),
          topRight: Radius.circular(10.0),
        ),
      ),
      builder: (BuildContext context) {
        isShow = true;
        return SingleChildScrollView(
          child: Container(
            // 返回一个有高度的组件,对话框高度就是此高度。
            padding: MediaQuery.of(context).viewInsets,
            height: 285,
            child: ListView(
              children: _bottomSheetList.map((e) => 
                Container(
                    decoration: BoxDecoration(
                      border: Border(
                        bottom: BorderSide(color: Color(0xFFe3e3e3)),
                      ),
                    ),
                    child: ListTile(
                      onTap: () => selectItemCallBack(e),
                      title: Text(e),
                    ),
                )).toList()
              ),
          );
        ),
      },
    );
    if (flag == null) {
      isShow = false;
    }
  }

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

  // 悬浮按钮,可以拖拽(可自定义样式)
  void _insertOverlay(BuildContext context) {
    entry = OverlayEntry(builder: (context) {
      final size = MediaQuery.of(context).size;
      double maxWidth = size.width - 50;
      double maxHeight = size.height - 50;
      double defaultX = size.width - 70;
      double defaultY = size.height - 310;
      return Positioned(
        top: draggable
            ? (moveOffset.dy < 0 // 沉浸式如果超出屏幕,默认为 0。
                ? 0
                : moveOffset.dy > maxHeight // 其他情况超出屏幕默认为 maxHeight。
                    ? maxHeight
                    : moveOffset.dy)
            : defaultY,
        left: draggable ? (moveOffset.dx > maxWidth ? maxWidth : moveOffset.dx) : defaultX,
        child: GestureDetector(
          // 移动开始
          onPanStart: (DragStartDetails details) {
            setState(() {
              lastStartOffset = details.globalPosition;
              dragAble = 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 - 70)), moveOffset.dy   (size.height - 310));
              }
            });
          },
          // 移动结束
          onPanEnd: (DragEndDetails detail) {
            setState(() {
              idleOffset = moveOffset * 1;
            });
          },
          child: BallContainer(
            onPressed: () => showSelectList(),
          ),
        ),
      );
    });
  }
}

/// 悬浮按钮的样式
class BallContainer extends StatelessWidget {
  final Function onPressed;
  BallContainer({this.onPressed});
  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.transparent,
      child: GestureDetector(
        onTap: onPressed,
        child: Container(
          width: 50,
          height: 50,
          alignment: Alignment.center,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            color: Color(0x666889E6),
          ),
          child: Text(
            '球体内容',
            style: TextStyle(color: Colors.white),
          ),
        ),
      ),
    );
  }
}

/// 关闭键盘,避免键盘与弹出列表冲突。
class KeyboardBack {
  static BuildContext context = navigatorKey.currentState.overlay.context;
  static FocusScopeNode currentFocus = FocusScope.of(context);
  static void keyboardBack() {
    if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) {
      FocusManager.instance.primaryFocus.unfocus();
    }
  }
}

使用

在主程序 main.dart 套上我们的 PubScaffold 即可。

代码语言:javascript复制
@override
Widget build(BuildContext context) {
  return MaterialApp(
      debugShowCheckedModeBanner: false,
      builder: EasyLoading.init(),
      home: PubScaffold(child: ...略)
  );
}

0 人点赞