flutter可吸边且隐藏一半的拖拽悬浮按钮

dart 复制代码
import 'package:flutter/material.dart';

class DraggableFloatingButton extends StatefulWidget {
  final Widget child;
  final double size; // 按钮大小
  final double hiddenRatio; // 隐藏比例(比如0.5表示隐藏一半)
  final GestureTapCallback? onTap;

  const DraggableFloatingButton({
    super.key,
    required this.child,
    this.size = 60,
    this.hiddenRatio = 0.5,
    this.onTap
  });

  @override
  State<DraggableFloatingButton> createState() => _DraggableFloatingButtonState();
}

class _DraggableFloatingButtonState extends State<DraggableFloatingButton> {
  late Offset _position; // 按钮当前位置
  late double _screenWidth; // 屏幕宽度
  late double _screenHeight; // 屏幕高度
  bool _isDragging = false; // 是否正在拖拽

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // 初始化位置(默认右下角)
    _screenWidth = MediaQuery.of(context).size.width;
    _screenHeight = MediaQuery.of(context).size.height;
    _position = Offset(
      _screenWidth - widget.size * (1 - widget.hiddenRatio),
      _screenHeight - widget.size * 6,
    );
  }

  // 处理拖拽结束后的吸边逻辑
  void _onDragEnd() {
    setState(() {
      // 判断靠左边还是右边吸边
      if (_position.dx < _screenWidth / 2) {
        // 靠左吸边,隐藏一半
        _position = Offset(-widget.size * widget.hiddenRatio, _position.dy);
      } else {
        // 靠右吸边,隐藏一半
        _position = Offset(
          _screenWidth - widget.size * (1 - widget.hiddenRatio),
          _position.dy,
        );
      }
      _isDragging = false;
    });
  }

  // 限制拖拽范围(避免超出屏幕)
  Offset _limitPosition(Offset newPosition) {
    double dx = newPosition.dx;
    double dy = newPosition.dy;

    // 水平范围:-隐藏部分 ~ 屏幕宽-显示部分
    dx = dx.clamp(
      -widget.size * widget.hiddenRatio,
      _screenWidth - widget.size * (1 - widget.hiddenRatio),
    );
    // 垂直范围:顶部安全区 ~ 底部安全区
    dy = dy.clamp(
      MediaQuery.of(context).padding.top + 10,
      _screenHeight - MediaQuery.of(context).padding.bottom - widget.size - 10,
    );

    return Offset(dx, dy);
  }

  @override
  Widget build(BuildContext context) {
    return Positioned(
      left: _position.dx,
      top: _position.dy,
      child: GestureDetector(
        onTap: widget.onTap,
        // 拖拽开始
        onPanStart: (details) {
          setState(() => _isDragging = true);
        },
        // 拖拽中
        onPanUpdate: (details) {
          setState(() {
            _position = _limitPosition(
              _position + details.delta,
            );
          });
        },
        // 拖拽结束
        onPanEnd: (details) => _onDragEnd(),
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeOut,
          width: widget.size,
          height: widget.size,
          // 拖拽时放大,未拖拽时复原
          transform: _isDragging
              ? Matrix4.diagonal3Values(1.2, 1.2, 1)//放大1.2倍
              : Matrix4.identity(),
          child: widget.child,
        ),
      ),
    );
  }

}

使用

dart 复制代码
@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          PageView.builder(
            controller: _pageController,
            padEnds: false,
            allowImplicitScrolling: true,
            scrollDirection: Axis.vertical,
            physics: CustomPageScrollPhysics(onLeftBoundaryReached: _leftHandleBoundaryReached,onRightBoundaryReached: _rightHandleBoundaryReached),
            itemCount: _goodsList.length,
            onPageChanged: (index) {
              setState(() {
                _currentPageIndex = index;
              });
              // 页面切换时更新视口占比
              //  _updateViewportFraction(index);
            },
            itemBuilder: (context, index) {
              return ProductSelectionVerticalPageItemWidget(index: index,
                goodsBean: _goodsList[index],
                marginBottom: index == _goodsList.length - 1 ? Utils().bottomStatusHeight() + 30.w : 0,);
            },
          ),
          // 可吸边悬浮按钮
          DraggableFloatingButton(
            size: 60,
            hiddenRatio: 0.5, // 隐藏一半
            onTap: (){},
            child: Container(
              width: 60,
              height: 60,
              decoration: BoxDecoration(
                color: Colors.white70,
                shape: BoxShape.circle,
                border: Border.all(color: JadeColors.grey_5)
              ),
              child: Icon(Icons.shopping_cart,color: JadeColors.grey_3,),
            ),
          ),
        ],
      )

    );
  }
相关推荐
火柴就是我7 小时前
学习一些常用的混合模式之BlendMode.srcIn
flutter
恋猫de小郭7 小时前
罗技鼠标因为服务器证书过期无法使用?我是如何解决 SSL 证书问题
android·前端·flutter
程序员老刘8 小时前
ArkUI-X 6.0 跨平台框架能否取代 Flutter?
flutter·客户端·arkui
火柴就是我8 小时前
学习一些常用的混合模式之BlendMode.srcOut
flutter
yfmingo10 小时前
flutter 哪些任务是在微队列,哪些是在事件队列
flutter
kirk_wang12 小时前
Flutter环境搭建与项目创建详解
flutter·移动开发·跨平台
西西学代码13 小时前
Flutter---动画
flutter
码农汉子14 小时前
构建属于自己的Flutter混合开发框架
flutter·dart