You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter:Bottom Sheet打开时如何通过手势移动替代容器

Flutter 替代容器拖动功能修复

你的代码里有个逻辑错误导致BottomSheet打开时替代容器无法拖动:替代容器的onPanUpdate里写反了判断条件,当_showAlternateContainer为true(也就是替代容器显示时),反而不会执行拖动逻辑。

具体问题点

在替代容器的GestureDetector中:

onPanUpdate: (details) {
  if (!_showAlternateContainer) { // 这里条件写反了!
    setState(() {
      top += details.delta.dy;
      left += details.delta.dx;
      print("container  move");
    });
  }
},

当替代容器显示时,_showAlternateContainer是true,所以!_showAlternateContainer为false,拖动逻辑根本不会执行。

修复方案

  1. 移除错误的条件判断,直接在onPanUpdate中更新位置
  2. 添加onPanDown事件,保持和原图片容器一致的选中状态逻辑,确保拖动时容器处于选中状态

修复后的完整代码如下:

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

// 假设GlobalState是你定义的全局状态类
class GlobalState {
  static List<MovableImageBox> movableAllWidgets = [];
  static int selectedIndex = 0;
  static ValueNotifier<bool> isBottomSheetVisible = ValueNotifier(false);
}

class MovableImageBox extends StatefulWidget {
  late final _MovableImageBoxState currentWidget;
  int index;

  MovableImageBox({required this.index});

  @override
  _MovableImageBoxState createState() {
    _MovableImageBoxState currentState = _MovableImageBoxState();
    this.currentWidget = currentState;
    return currentState;
  }
}

const ballDiameter = 15.0;
const rotateIconSize = 30.0;

class _MovableImageBoxState extends State<MovableImageBox> {
  double width = 300;
  double height = 200.0;
  double aspectRatio = 200 / 300;
  bool _isSelected = true;
  double top = 100;
  double left = 100;
  bool _showAlternateContainer = false;

  // 补充原代码中调用但未实现的方法
  void updateIsSelected(bool value) {
    setState(() {
      _isSelected = value;
    });
  }

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(children: [
      Positioned(
        top: top,
        left: left,
        child: GestureDetector(
          onTap: () {
            for (var i = 0; i < GlobalState.movableAllWidgets.length; i++) {
              print("_addMovableTextBox $i");
              GlobalState.movableAllWidgets[i].currentWidget
                  .updateIsSelected(false);
            }
            setState(() {
              _isSelected = true;
              GlobalState.selectedIndex = widget.index;
              print("index = ${GlobalState.selectedIndex}");
            });
          },
          onPanDown: (details) {
            print("down time width =$width");
            print("down time height =$height");
            for (var i = 0; i < GlobalState.movableAllWidgets.length; i++) {
              print("_addMovableTextBox $i");
              GlobalState.movableAllWidgets[i].currentWidget
                  .updateIsSelected(false);
            }
            setState(() {
              FocusScope.of(context).unfocus();
              _isSelected = true;
            });
          },
          onPanUpdate: (details) {
            setState(() {
              top += details.delta.dy;
              left += details.delta.dx;
            });
          },
          onDoubleTap: () {
            _showBottomSheet();
          },
          child: Container(
            height: height,
            width: width,
            padding: EdgeInsets.all(2.0),
            decoration: BoxDecoration(
              color: Colors.transparent,
              border: Border.all(
                width: 2,
                color: _isSelected ? Colors.black : Colors.transparent,
              ),
            ),
            child: ClipRect(
              child: Align(
                alignment: Alignment.topCenter,
                child: Image.asset(
                  'images/rose1.png',
                  width: width,
                  height: height,
                  fit: BoxFit.cover,
                ),
              ),
            ),
          ),
        ),
      ),
      if (_showAlternateContainer)
        Positioned(
          top: top,
          left: left,
          child: GestureDetector(
            // 保持选中状态逻辑一致
            onPanDown: (details) {
              for (var i = 0; i < GlobalState.movableAllWidgets.length; i++) {
                GlobalState.movableAllWidgets[i].currentWidget
                    .updateIsSelected(false);
              }
              setState(() {
                _isSelected = true;
              });
            },
            // 移除错误条件,直接执行拖动逻辑
            onPanUpdate: (details) {
              setState(() {
                top += details.delta.dy;
                left += details.delta.dx;
                print("container  move");
              });
            },
            child: Container(
              height: height - 20,
              width: width - 20,
              decoration: BoxDecoration(
                color: Colors.transparent,
                border: Border.all(
                  width: 2,
                  color: Colors.black,
                ),
              ),
              child: Center(
                child: Text(
                  'Alternate Container',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
          ),
        ),
    ]);
  }

  void _showBottomSheet() {
    setState(() {
      GlobalState.isBottomSheetVisible.value = true;
      _showAlternateContainer = true;
    });
    showModalBottomSheet(
      context: context,
      barrierColor: Colors.black.withAlpha(50),
      builder: (BuildContext context) {
        return Container(
          height: 230,
          child: Center(
            child: Text("This is a bottom sheet"),
          ),
        );
      },
    ).whenComplete(() {
      print("${GlobalState.isBottomSheetVisible}");
      setState(() {
        GlobalState.isBottomSheetVisible.value = false;
        _showAlternateContainer = false;
      });
      print("${GlobalState.isBottomSheetVisible}");
    });
  }
}

额外说明

  • 补充了updateIsSelected方法(原代码中调用但未实现)
  • 统一了替代容器的边框颜色逻辑,避免冗余判断
  • 在替代容器的onPanDown中添加了选中状态处理,和原图片容器保持一致逻辑

内容的提问来源于stack exchange,提问作者Harsh Rajpara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 01:15:11