Last active
October 14, 2025 07:42
-
-
Save chooyan-eng/20b7eec58104b5f98693e844865a2746 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:animated_to/animated_to.dart'; | |
| import 'package:flutter/material.dart'; | |
| void main() => runApp(MaterialApp(home: DraggableDemoPage())); | |
| class DraggableDemoPage extends StatefulWidget { | |
| const DraggableDemoPage({super.key}); | |
| @override | |
| State<DraggableDemoPage> createState() => _DraggableDemoPageState(); | |
| } | |
| /// ひとつひとつのアイテムに必要な情報を保持するクラス | |
| class _Item { | |
| _Item({required this.id, required this.color}); | |
| final String id; | |
| final Color color; | |
| } | |
| class _DraggableDemoPageState extends State<DraggableDemoPage> { | |
| /// 20 個分を機械的に生成 | |
| final _cubes = List.generate( | |
| 20, | |
| (index) => _Item( | |
| id: index.toString(), | |
| color: Colors.primaries[index % Colors.primaries.length], | |
| ), | |
| ); | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| body: Center( | |
| child: Padding( | |
| padding: const EdgeInsets.symmetric(horizontal: 16), | |
| child: Wrap( | |
| spacing: 12, | |
| runSpacing: 12, | |
| children: _cubes | |
| .map( | |
| (e) => _Cube( | |
| item: e, | |
| // ドラッグ&ドロップ中、他のアイテムの上に指が来たタイミングで呼び出されるコールバック | |
| onAccept: (data) { | |
| final targetIndex = _cubes.indexOf(e); // 移動先のインデックス | |
| final draggingIndex = _cubes.indexOf(data); // 移動元(つまりドラッグしているアイテム)のインデックス | |
| setState(() { | |
| _cubes | |
| ..removeAt(draggingIndex) // 移動元から消して | |
| ..insert(targetIndex, data); // 移動先に追加 | |
| }); | |
| }, | |
| ), | |
| ) | |
| .toList(), | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| /// ドラッグ&ドロップで並べ替え可能な丸(見た目は適宜修正してください) | |
| class _Cube extends StatelessWidget { | |
| const _Cube({required this.item, required this.onAccept}); | |
| final _Item item; | |
| final ValueSetter<_Item> onAccept; | |
| @override | |
| Widget build(BuildContext context) { | |
| // AnimatedTo で移動時にアニメーションさせる | |
| return AnimatedTo.curve( | |
| globalKey: GlobalObjectKey(item.id), | |
| child: DragTarget( | |
| // 指が上に来た時に呼び出される(ドロップしたかどうかは関係ない) | |
| onWillAcceptWithDetails: (details) { | |
| if (details.data == item) { | |
| return false; | |
| } | |
| onAccept(details.data as _Item); | |
| return true; | |
| }, | |
| builder: (context, candidateData, rejectedData) => Draggable( | |
| feedback: _CubeFace(color: item.color, size: 60), | |
| childWhenDragging: _CubeFace(color: Colors.transparent), | |
| data: item, | |
| child: _CubeFace(color: item.color), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| /// ドラッグ中の見た目(Draggable.feedback)と元の見た目(Draggable.child)は同一のため、 | |
| /// 共通のWidgetとして切り出しています。 | |
| class _CubeFace extends StatelessWidget { | |
| const _CubeFace({required this.color, this.size = 60}); | |
| final Color color; | |
| final double size; | |
| @override | |
| Widget build(BuildContext context) { | |
| return Container( | |
| width: size, | |
| height: size, | |
| decoration: BoxDecoration(color: color, shape: BoxShape.circle), | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment