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

Flutter可拖拽缩放容器:如何实现全点位缩放功能

实现Flutter容器全点位缩放控制

要完成8个点位的完整缩放,核心是根据每个控制点的位置,计算拖动时容器的宽高变化,同时保持预设宽高比,并且调整容器的坐标(x/y)匹配拖动轨迹。以下是补全所有点位逻辑后的完整代码:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: SafeArea(
          child: ImageManager(),
        ),
      ),
    );
  }
}

final ballRadius = 7.5;

class ImageManager extends StatefulWidget {
  @override
  _ImageManagerState createState() => _ImageManagerState();
}

class _ImageManagerState extends State<ImageManager> {
  double _x = 0;
  double _y = 0;

  double _height = 200;
  double _width = 300;

  double _aspectRatio = 200 / 300;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          top: _y,
          left: _x,
          child: GestureDetector(
            onPanUpdate: (DragUpdateDetails details) {
              setState(() {
                _x += details.delta.dx;
                _y += details.delta.dy;
              });
            },
            child: Image.network(
              "https://via.placeholder.com/300x200",
              width: _width,
              fit: BoxFit.fill,
            ),
          ),
        ),

        // 左上角
        Positioned(
          top: _y - ballRadius,
          left: _x - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {
              var newWidth = _width - dx;
              var newHeight = newWidth * _aspectRatio;
              setState(() {
                _y = _y + (_height - newHeight);
                _x = _x + dx;
                _width = newWidth;
                _height = newHeight;
              });
            },
            onDragEnd: () {},
          ),
        ),

        // 上中
        Positioned(
          top: _y - ballRadius,
          left: _x + _width / 2 - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {
              var newHeight = _height - dy;
              var newWidth = newHeight / _aspectRatio;
              setState(() {
                _y += dy;
                _height = newHeight;
                _width = newWidth;
              });
            },
            onDragEnd: () {},
          ),
        ),

        // 右上角
        Positioned(
          top: _y - ballRadius,
          left: _x + _width - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {
              var newWidth = _width + dx;
              var newHeight = newWidth * _aspectRatio;
              setState(() {
                _y += (_height - newHeight);
                _width = newWidth;
                _height = newHeight;
              });
            },
            onDragEnd: () {},
          ),
        ),

        // 左中
        Positioned(
          top: _y + _height / 2 - ballRadius,
          left: _x - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {
              var newWidth = _width - dx;
              var newHeight = newWidth * _aspectRatio;
              setState(() {
                _x += dx;
                _width = newWidth;
                _height = newHeight;
              });
            },
            onDragEnd: () {},
          ),
        ),

        // 右中
        Positioned(
          top: _y + _height / 2 - ballRadius,
          left: _x + _width - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {
              var newWidth = _width + dx;
              var newHeight = newWidth * _aspectRatio;
              setState(() {
                _width = newWidth;
                _height = newHeight;
              });
            },
            onDragEnd: () {},
          ),
        ),

        // 左下角
        Positioned(
          top: _y + _height - ballRadius,
          left: _x - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {
              var newWidth = _width - dx;
              var newHeight = newWidth * _aspectRatio;
              setState(() {
                _x += dx;
                _width = newWidth;
                _height = newHeight;
              });
            },
            onDragEnd: () {},
          ),
        ),

        // 下中
        Positioned(
          top: _y + _height - ballRadius,
          left: _x + _width / 2 - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {
              var newHeight = _height + dy;
              var newWidth = newHeight / _aspectRatio;
              setState(() {
                _height = newHeight;
                _width = newWidth;
              });
            },
            onDragEnd: () {},
          ),
        ),

        // 右下角
        Positioned(
          top: _y + _height - ballRadius,
          left: _x + _width - ballRadius,
          child: Ball(
            onDragStart: () {},
            onDrag: (double dx, double dy) {
              var newWidth = _width + dx;
              var newHeight = newWidth * _aspectRatio;
              setState(() {
                _width = newWidth;
                _height = newHeight;
              });
            },
            onDragEnd: () {},
          ),
        ),
      ],
    );
  }
}

class Ball extends StatelessWidget {
  final Function onDragStart;
  final Function onDrag;
  final Function onDragEnd;

  const Ball({required this.onDragStart, required this.onDrag, required this.onDragEnd});

  void _onDragStart(DragStartDetails details) {
    if (onDragStart != null) onDragStart();
  }

  void _onDragUpdate(DragUpdateDetails details) {
    if (onDrag != null) onDrag(details.delta.dx, details.delta.dy);
  }

  void _onDragEnd(DragEndDetails details) {
    if (onDragEnd != null) onDragEnd();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanStart: _onDragStart,
      onPanUpdate: _onDragUpdate,
      onPanEnd: _onDragEnd,
      child: Container(
        height: 2 * ballRadius,
        width: 2 * ballRadius,
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.circular(ballRadius),
          border: Border.all(
            width: 3,
            color: Colors.white,
          ),
        ),
      ),
    );
  }
}

各点位缩放逻辑说明

  • 上中:垂直拖动时,通过dy计算新高度,再按宽高比推导新宽度,同时调整容器顶部_y坐标匹配拖动位置。
  • 右上角:水平拖动改变宽度,按宽高比计算新高度,调整容器顶部_y坐标以保持比例,右侧宽度随拖动扩展。
  • 左中:水平拖动改变左侧位置,通过dx计算新宽度,再按比例得到新高度,容器_x坐标随拖动调整。
  • 右中:水平拖动直接扩展宽度,按宽高比自动调整高度,容器位置保持不变。
  • 左下角:水平拖动改变左侧位置,计算新宽度后按比例得到新高度,容器_x坐标随拖动调整。
  • 下中:垂直拖动扩展高度,按宽高比推导新宽度,容器底部位置随拖动扩展。

所有点位的缩放都严格遵循预设的_aspectRatio,确保容器始终保持初始宽高比例。

内容的提问来源于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.28 11:45:54