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

Flutter 实现手势水平拖拽调整尺寸的Container组件

实现方案

你之前写的代码跑起来效果不对,核心是两个问题:

  • 每次拖拽回调触发时固定给宽度加减1,完全没用到手势返回的实际拖拽位移值,宽度变化和手指移动的距离、速度完全不匹配,自然手感卡顿、跟手性差
  • 左右拖拽手柄的逻辑没做区分:拖拽左边缘调整尺寸时,必须同步修改容器的水平位置,不然拉伸方向会和拖拽方向反过来

下面是完整可用的实现,同时支持单边拖拽调宽度、整体拖拽移动容器,全程跟手,自带最小宽度和屏幕边界限制,不会拖出可视区域:

import 'package:flutter/material.dart';
import 'dart:math' as math;

class ResizableContainer extends StatefulWidget {
  const ResizableContainer({super.key});

  @override
  State<ResizableContainer> createState() => _ResizableContainerState();
}

class _ResizableContainerState extends State<ResizableContainer> {
  // 容器初始位置和尺寸
  double containerLeft = 100;
  double containerWidth = 200;
  final double containerHeight = 40;
  final double minWidth = 80; // 可自定义最小宽度限制

  @override
  Widget build(BuildContext context) {
    final screenWidth = MediaQuery.of(context).size.width;

    return Scaffold(
      body: SafeArea(
        child: Stack(
          children: [
            Positioned(
              left: containerLeft,
              top: 100,
              width: containerWidth,
              height: containerHeight,
              child: Container(
                decoration: BoxDecoration(
                  color: Theme.of(context).primaryColor,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Row(
                  children: [
                    // 左侧拖拽手柄
                    GestureDetector(
                      onHorizontalDragUpdate: (details) {
                        final dx = details.delta.dx;
                        double newLeft = containerLeft + dx;
                        double newWidth = containerWidth - dx;

                        // 边界限制:不超出屏幕左边缘、宽度不小于最小值
                        if (newLeft < 0) {
                          newWidth = newWidth + newLeft;
                          newLeft = 0;
                        }
                        if (newWidth < minWidth) {
                          newLeft = newLeft - (minWidth - newWidth);
                          newWidth = minWidth;
                        }

                        setState(() {
                          containerLeft = newLeft;
                          containerWidth = newWidth;
                        });
                      },
                      child: const SizedBox(
                        width: 32,
                        child: Icon(
                          Icons.navigate_before_rounded,
                          color: Colors.white,
                        ),
                      ),
                    ),
                    // 中间整体拖拽区域
                    Expanded(
                      child: GestureDetector(
                        onHorizontalDragUpdate: (details) {
                          final dx = details.delta.dx;
                          double newLeft = containerLeft + dx;
                          // 边界限制:不拖出屏幕左右侧
                          newLeft = math.max(0, newLeft);
                          newLeft = math.min(newLeft, screenWidth - containerWidth);
                          
                          setState(() {
                            containerLeft = newLeft;
                          });
                        },
                        child: const Center(
                          child: Text(
                            "拖拽移动",
                            style: TextStyle(color: Colors.white, fontSize: 12),
                          ),
                        ),
                      ),
                    ),
                    // 右侧拖拽手柄
                    GestureDetector(
                      onHorizontalDragUpdate: (details) {
                        final dx = details.delta.dx;
                        double newWidth = containerWidth + dx;
                        // 边界限制:不小于最小宽度、不超出屏幕右边缘
                        newWidth = math.max(minWidth, newWidth);
                        newWidth = math.min(newWidth, screenWidth - containerLeft);

                        setState(() {
                          containerWidth = newWidth;
                        });
                      },
                      child: const SizedBox(
                        width: 32,
                        child: Icon(
                          Icons.navigate_next_rounded,
                          color: Colors.white,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

核心逻辑说明

  • 用Stack+Positioned承载容器,通过修改left(水平偏移)和width(宽度)两个属性实现位置和尺寸变化,布局逻辑更简单
  • 拖拽计算直接使用手势返回的details.delta.dx(手指单次移动的实际像素距离),保证宽度/位置变化和手指移动完全同步,没有卡顿感
  • 所有拖拽逻辑都加了边界判断:容器不会被拖出屏幕可视范围,宽度不会小于设定的最小值
  • 左右手柄单独处理拖拽逻辑:拖左手柄同步修改位置和宽度,拖右手柄仅修改宽度,中间区域响应整体拖拽移动
  • 手柄区域用固定尺寸的SizedBox包裹,扩大触摸热区,避免图标太小点不准、拖不动的问题

内容的提问来源于stack exchange,提问作者Raj Dhakad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 03:06:16