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
相关产品推荐
相关产品推荐

