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,),
            ),
          ),
        ],
      )

    );
  }
相关推荐
铅笔侠_小龙虾1 小时前
Flutter 实战: 计算器
开发语言·javascript·flutter
微祎_2 小时前
Flutter for OpenHarmony:构建一个 Flutter 重力弹球游戏,2D 物理引擎、手势交互与关卡设计的工程实现
flutter·游戏·交互
一起养小猫3 小时前
Flutter for OpenHarmony 实战_魔方应用UI设计与交互优化
flutter·ui·交互·harmonyos
hudawei9963 小时前
flutter和Android动画的对比
android·flutter·动画
一只大侠的侠3 小时前
Flutter开源鸿蒙跨平台训练营 Day7Flutter+ArkTS双方案实现轮播图+搜索框+导航组件
flutter·开源·harmonyos
一只大侠的侠4 小时前
Flutter开源鸿蒙跨平台训练营 Day9分类数据的获取与渲染实现
flutter·开源·harmonyos
一只大侠的侠4 小时前
Flutter开源鸿蒙跨平台训练营 Day 5Flutter开发鸿蒙电商应用
flutter·开源·harmonyos
ZH15455891315 小时前
Flutter for OpenHarmony Python学习助手实战:GUI桌面应用开发的实现
python·学习·flutter
一只大侠的侠6 小时前
Flutter开源鸿蒙跨平台训练营 Day6ArkUI框架实战
flutter·开源·harmonyos
一只大侠的侠7 小时前
Flutter开源鸿蒙跨平台训练营 Day 4实现流畅的下拉刷新与上拉加载效果
flutter·开源·harmonyos