Skip to content

Instantly share code, notes, and snippets.

@chooyan-eng
Last active October 14, 2025 07:42
Show Gist options
  • Select an option

  • Save chooyan-eng/20b7eec58104b5f98693e844865a2746 to your computer and use it in GitHub Desktop.

Select an option

Save chooyan-eng/20b7eec58104b5f98693e844865a2746 to your computer and use it in GitHub Desktop.
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