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

