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

Flutter中如何将Draggable组件改为点击方式移动至DragTarget?

点击选择Lottie元素并移动到指定单元格的实现方案

问题说明

已通过Draggable和DragTarget实现Lottie元素的拖拽移动,现在需要改为点击选择元素后,点击目标单元格完成移动的交互方式,同时需要明确Draggable的onDragStarted属性的正确用法。

错误代码分析

原代码中DragBox的onDragStarted回调存在误用:在该回调内直接构建DragTarget组件是错误的,onDragStarted仅用于拖拽开始时的状态通知(比如更新UI状态、记录拖拽起始信息),不能用来渲染UI组件。

修正后的完整代码

import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';

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

  @override
  State<Screen6_1_3> createState() => _Screen6_1_3State();
}

class _Screen6_1_3State extends State<Screen6_1_3> {
  LottieBuilder? caughtAnimation1;
  LottieBuilder? caughtAnimation2;
  LottieBuilder? caughtAnimation3;
  // 维护当前选中的动画元素
  LottieBuilder? selectedAnimation;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.blue,
      appBar: AppBar(
        centerTitle: true,
        backgroundColor: Colors.blue,
        leading: GestureDetector(
          child: const Icon(Icons.close, color: Colors.white),
          onTap: () {
            Navigator.pushNamedAndRemoveUntil(context, "home", (route) => false);
          },
        ),
      ),
      body: Column(
        children: [
          // 目标单元格行
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 目标单元格1
              GestureDetector(
                onTap: () {
                  if (selectedAnimation != null) {
                    setState(() {
                      caughtAnimation1 = selectedAnimation;
                      selectedAnimation = null;
                    });
                  }
                },
                child: DragTarget(
                  // 保留拖拽兼容(可选)
                  onAccept: (LottieBuilder animation) {
                    setState(() => caughtAnimation1 = animation);
                  },
                  builder: (context, candidateData, rejectedData) {
                    return Container(
                      height: 60,
                      width: 60,
                      decoration: BoxDecoration(
                        color: Colors.grey.shade400.withOpacity(0.5),
                        borderRadius: BorderRadius.circular(20.0),
                        // 选中状态时高亮边框
                        border: selectedAnimation != null
                            ? Border.all(color: Colors.white, width: 2)
                            : null,
                      ),
                      child: caughtAnimation1,
                    );
                  },
                ),
              ),
              const Padding(padding: EdgeInsets.all(8.0)),
              // 目标单元格2
              GestureDetector(
                onTap: () {
                  if (selectedAnimation != null) {
                    setState(() {
                      caughtAnimation2 = selectedAnimation;
                      selectedAnimation = null;
                    });
                  }
                },
                child: DragTarget(
                  onAccept: (LottieBuilder animation) {
                    setState(() => caughtAnimation2 = animation);
                  },
                  builder: (context, candidateData, rejectedData) {
                    return Container(
                      height: 60,
                      width: 60,
                      decoration: BoxDecoration(
                        color: Colors.grey.shade400.withOpacity(0.5),
                        borderRadius: BorderRadius.circular(20.0),
                        border: selectedAnimation != null
                            ? Border.all(color: Colors.white, width: 2)
                            : null,
                      ),
                      child: caughtAnimation2,
                    );
                  },
                ),
              ),
              const Padding(padding: EdgeInsets.all(8.0)),
              // 目标单元格3
              GestureDetector(
                onTap: () {
                  if (selectedAnimation != null) {
                    setState(() {
                      caughtAnimation3 = selectedAnimation;
                      selectedAnimation = null;
                    });
                  }
                },
                child: DragTarget(
                  onAccept: (LottieBuilder animation) {
                    setState(() => caughtAnimation3 = animation);
                  },
                  builder: (context, candidateData, rejectedData) {
                    return Container(
                      height: 60,
                      width: 60,
                      decoration: BoxDecoration(
                        color: Colors.grey.shade400.withOpacity(0.5),
                        borderRadius: BorderRadius.circular(20.0),
                        border: selectedAnimation != null
                            ? Border.all(color: Colors.white, width: 2)
                            : null,
                      ),
                      child: caughtAnimation3,
                    );
                  },
                ),
              ),
            ],
          ),
          const SizedBox(height: 20),
          // 可选择的Lottie元素行
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                width: 200,
                height: 60,
                decoration: BoxDecoration(
                  color: Colors.grey.shade400.withOpacity(0.5),
                  borderRadius: BorderRadius.circular(20.0),
                ),
                child: ListView(
                  scrollDirection: Axis.horizontal,
                  children: [
                    const SizedBox(width: 15),
                    // 可点击选择的Lottie元素
                    GestureDetector(
                      onTap: () {
                        setState(() {
                          selectedAnimation = Lottie.asset('assets/sunny.json');
                        });
                      },
                      child: Container(
                        width: 40,
                        height: 50,
                        child: Lottie.asset('assets/sunny.json'),
                        // 选中时高亮
                        decoration: selectedAnimation?.toString() == Lottie.asset('assets/sunny.json').toString()
                            ? BoxDecoration(
                                border: Border.all(color: Colors.white, width: 2),
                                borderRadius: BorderRadius.circular(10),
                              )
                            : null,
                      ),
                    ),
                    const SizedBox(width: 15),
                    GestureDetector(
                      onTap: () {
                        setState(() {
                          selectedAnimation = Lottie.asset('assets/cat.json');
                        });
                      },
                      child: Container(
                        width: 40,
                        height: 50,
                        child: Lottie.asset('assets/cat.json'),
                        decoration: selectedAnimation?.toString() == Lottie.asset('assets/cat.json').toString()
                            ? BoxDecoration(
                                border: Border.all(color: Colors.white, width: 2),
                                borderRadius: BorderRadius.circular(10),
                              )
                            : null,
                      ),
                    ),
                    const SizedBox(width: 15),
                    GestureDetector(
                      onTap: () {
                        setState(() {
                          selectedAnimation = Lottie.asset('assets/scanning.json');
                        });
                      },
                      child: Container(
                        width: 40,
                        height: 50,
                        child: Lottie.asset('assets/scanning.json'),
                        decoration: selectedAnimation?.toString() == Lottie.asset('assets/scanning.json').toString()
                            ? BoxDecoration(
                                border: Border.all(color: Colors.white, width: 2),
                                borderRadius: BorderRadius.circular(10),
                              )
                            : null,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

onDragStarted的正确用法

Draggable的onDragStarted是拖拽动作开始时的回调函数(默认是长按触发拖拽),仅用于执行状态更新或逻辑处理,示例:

Draggable(
  onDragStarted: () {
    // 拖拽开始时执行:比如隐藏原元素、记录拖拽状态、弹出提示等
    setState(() {
      isDragging = true;
    });
  },
  feedback: Container(child: Lottie.asset('assets/sunny.json')),
  child: isDragging ? Container() : Lottie.asset('assets/sunny.json'),
  data: Lottie.asset('assets/sunny.json'),
)

它的核心作用是通知业务逻辑拖拽已启动,不能在该回调内构建UI组件,所有UI都应该放在build方法中通过状态变量控制渲染。

内容的提问来源于stack exchange,提问作者Igor Boldyrev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 22:05:27