赤いボックス(Container)が表示される
それをフリックすると、print() でメッセージが出ます(デバッグコンソールに)
title.rb
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: MyGestureWrapper(
flick: FlickAction(
child: ColoredBoxWidget(),
onFlick: handleFlick,
),
),
),
),
);
}
static void handleFlick() {
print('フリックされました!');
}
}
//-------------------- FlickAction と Wrapper -------------------------
class FlickAction {
final Widget child;
final VoidCallback onFlick;
const FlickAction({required this.child, required this.onFlick});
}
class MyGestureWrapper extends StatelessWidget {
final FlickAction flick;
const MyGestureWrapper({super.key, required this.flick});
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanEnd: (details) {
final velocity = details.velocity.pixelsPerSecond;
if (velocity.distance > 1000) {
flick.onFlick();
}
},
child: DraggableFlickableWidget(child: flick.child),
);
}
}
//-------------------- シンプルなドラッグ対応 Widget -------------------------
class DraggableFlickableWidget extends StatefulWidget {
final Widget child;
const DraggableFlickableWidget({super.key, required this.child});
@override
State<DraggableFlickableWidget> createState() => _DraggableFlickableWidgetState();
}
class _DraggableFlickableWidgetState extends State<DraggableFlickableWidget> {
Offset _offset = Offset.zero;
@override
Widget build(BuildContext context) {
return Transform.translate(
offset: _offset,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
_offset += details.delta;
});
},
child: widget.child,
),
);
}
}
//-------------------- テスト用の赤いボックス -------------------------
class ColoredBoxWidget extends StatelessWidget {
const ColoredBoxWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: 120,
height: 120,
color: Colors.red,
alignment: Alignment.center,
child: const Text('フリックしてね', style: TextStyle(color: Colors.white)),
);
}
}
速い操作 → フリック(onFlick を呼ぶ)
遅い操作 → ドラッグ(移動だけ)
title.rb
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: MyGestureWrapper(
flick: FlickAction(
child: ColoredBoxWidget(),
onFlick: handleFlick,
),
),
),
),
);
}
static void handleFlick() {
print('✨ フリックされました!');
}
}
//-------------------- FlickAction と Wrapper -------------------------
class FlickAction {
final Widget child;
final VoidCallback onFlick;
const FlickAction({required this.child, required this.onFlick});
}
class MyGestureWrapper extends StatefulWidget {
final FlickAction flick;
const MyGestureWrapper({super.key, required this.flick});
@override
State<MyGestureWrapper> createState() => _MyGestureWrapperState();
}
class _MyGestureWrapperState extends State<MyGestureWrapper> {
Offset _offset = Offset.zero;
final double flickThreshold = 1000.0; // px/sec 以上でフリックと判定
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (details) {
// 移動中は常にオフセットを更新(ドラッグ)
setState(() {
_offset += details.delta;
});
},
onPanEnd: (details) {
final velocity = details.velocity.pixelsPerSecond;
final speed = velocity.distance;
print('速度: ${speed.toStringAsFixed(2)} px/s');
if (speed > flickThreshold) {
widget.flick.onFlick();
} else {
print('🔄 ドラッグでした');
}
},
child: Transform.translate(
offset: _offset,
child: widget.flick.child,
),
);
}
}
//-------------------- テスト用の赤いボックス -------------------------
class ColoredBoxWidget extends StatelessWidget {
const ColoredBoxWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: 120,
height: 120,
color: Colors.red,
alignment: Alignment.center,
child: const Text('フリック or ドラッグ', style: TextStyle(color: Colors.white)),
);
}
}
onLongPressStart → ロングタップ開始時に isDragging = true
onPanUpdate → isDragging == true のときのみオフセット更新
onPanEnd → 速度が速ければフリック、そうでなければ何もしない(ドラッグ終了)
title.rb
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: MyGestureWrapper(
flick: FlickAction(
child: ColoredBoxWidget(),
onFlick: handleFlick,
),
),
),
),
);
}
static void handleFlick() {
print('✨ フリックされました!');
}
}
//-------------------- FlickAction と Wrapper -------------------------
class FlickAction {
final Widget child;
final VoidCallback onFlick;
const FlickAction({required this.child, required this.onFlick});
}
class MyGestureWrapper extends StatefulWidget {
final FlickAction flick;
const MyGestureWrapper({super.key, required this.flick});
@override
State<MyGestureWrapper> createState() => _MyGestureWrapperState();
}
class _MyGestureWrapperState extends State<MyGestureWrapper> {
Offset _offset = Offset.zero;
bool isDragging = false;
final double flickThreshold = 1000.0; // px/sec 以上でフリックと判定
@override
Widget build(BuildContext context) {
return GestureDetector(
onLongPressStart: (_) {
// ロングタップされたらドラッグモードに入る
isDragging = true;
print('🟡 ロングタップ開始 → ドラッグ許可');
},
onPanUpdate: (details) {
if (isDragging) {
setState(() {
_offset += details.delta;
});
}
},
onPanEnd: (details) {
final velocity = details.velocity.pixelsPerSecond;
final speed = velocity.distance;
print('📈 終了時速度: ${speed.toStringAsFixed(2)} px/s');
if (speed > flickThreshold) {
widget.flick.onFlick(); // フリック
} else if (isDragging) {
print('🔄 ロングタップ+ドラッグでした');
}
isDragging = false; // モード解除
},
child: Transform.translate(
offset: _offset,
child: widget.flick.child,
),
);
}
}
//-------------------- テスト用の赤いボックス -------------------------
class ColoredBoxWidget extends StatelessWidget {
const ColoredBoxWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: 140,
height: 140,
color: Colors.red,
alignment: Alignment.center,
child: const Text(
'ロングタップしてドラッグ\nまたはフリック',
style: TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
);
}
}
赤いボックスを画像に差し替えたテストコード
title.rb
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: MyGestureWrapper(
flick: FlickAction(
child: ImageBoxWidget(),
onFlick: handleFlick,
),
),
),
),
);
}
static void handleFlick() {
print('✨ フリックされました!(画像)');
}
}
//-------------------- FlickAction と Gesture Wrapper -------------------------
class FlickAction {
final Widget child;
final VoidCallback onFlick;
const FlickAction({required this.child, required this.onFlick});
}
class MyGestureWrapper extends StatefulWidget {
final FlickAction flick;
const MyGestureWrapper({super.key, required this.flick});
@override
State<MyGestureWrapper> createState() => _MyGestureWrapperState();
}
class _MyGestureWrapperState extends State<MyGestureWrapper> {
Offset _offset = Offset.zero;
bool isDragging = false;
final double flickThreshold = 1000.0; // px/sec 以上でフリックと判定
@override
Widget build(BuildContext context) {
return GestureDetector(
onLongPressStart: (_) {
isDragging = true;
print('🟡 ロングタップ開始 → ドラッグ許可');
},
onPanUpdate: (details) {
if (isDragging) {
setState(() {
_offset += details.delta;
});
}
},
onPanEnd: (details) {
final velocity = details.velocity.pixelsPerSecond;
final speed = velocity.distance;
print('📈 終了時速度: ${speed.toStringAsFixed(2)} px/s');
if (speed > flickThreshold) {
widget.flick.onFlick();
} else if (isDragging) {
print('🔄 ロングタップ+ドラッグでした');
}
isDragging = false;
},
child: Transform.translate(
offset: _offset,
child: widget.flick.child,
),
);
}
}
//-------------------- テスト用の画像 -------------------------
class ImageBoxWidget extends StatelessWidget {
const ImageBoxWidget({super.key});
@override
Widget build(BuildContext context) {
return Image.network(
'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg',
width: 200,
height: 200,
fit: BoxFit.cover,
);
}
}
MyGestureWrapper に VoidCallback? onTap を追加
GestureDetector の onTap にバインド
title.rb
class MyGestureWrapper extends StatefulWidget {
final FlickAction flick;
final VoidCallback? onTap; // 👈 追加
const MyGestureWrapper({
super.key,
required this.flick,
this.onTap, // 👈 追加
});
@override
State<MyGestureWrapper> createState() => _MyGestureWrapperState();
}
class _MyGestureWrapperState extends State<MyGestureWrapper> {
Offset _offset = Offset.zero;
bool isDragging = false;
final double flickThreshold = 1000.0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onTap, // 👈 追加された onTap を GestureDetector に渡す
onLongPressStart: (_) {
isDragging = true;
print('🟡 ロングタップ → ドラッグ許可');
},
onPanUpdate: (details) {
if (isDragging) {
setState(() {
_offset += details.delta;
});
}
},
onPanEnd: (details) {
final speed = details.velocity.pixelsPerSecond.distance;
print('📈 終了時速度: ${speed.toStringAsFixed(2)} px/s');
if (speed > flickThreshold) {
widget.flick.onFlick();
} else if (isDragging) {
print('🔄 ロングタップ+ドラッグでした');
}
isDragging = false;
},
child: Transform.translate(
offset: _offset,
child: widget.flick.child,
),
);
}
}
// 利用側
MyGestureWrapper(
flick: FlickAction(
child: ImageBoxWidget(),
onFlick: () => print("🌀 フリック!"),
),
onTap: () => print("👆 タップされました!"),
)
Direction enum を定義
FlickAction に Function(Direction) 型の onFlick を渡せるよう変更
フリック方向をベクトルから判定(XとYの絶対値比較)
title.rb
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: MyGestureWrapper(
flick: FlickAction(
child: ImageBoxWidget(),
onFlick: _handleFlick,
),
onTap: _handleTap,
),
),
),
);
}
static void _handleFlick(Direction direction) {
print('🌀 フリックされました → $direction');
}
static void _handleTap() {
print('👆 タップされました!');
}
}
//-------------------- Direction Enum -------------------------
enum Direction { up, down, left, right }
//-------------------- FlickAction -------------------------
class FlickAction {
final Widget child;
final void Function(Direction direction) onFlick;
const FlickAction({
required this.child,
required this.onFlick,
});
}
//-------------------- MyGestureWrapper -------------------------
class MyGestureWrapper extends StatefulWidget {
final FlickAction flick;
final VoidCallback? onTap;
const MyGestureWrapper({
super.key,
required this.flick,
this.onTap,
});
@override
State<MyGestureWrapper> createState() => _MyGestureWrapperState();
}
class _MyGestureWrapperState extends State<MyGestureWrapper> {
Offset _offset = Offset.zero;
bool isDragging = false;
final double flickThreshold = 1000.0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onTap,
onLongPressStart: (_) {
isDragging = true;
print('🟡 ロングタップ開始 → ドラッグ許可');
},
onPanUpdate: (details) {
if (isDragging) {
setState(() {
_offset += details.delta;
});
final direction = _getDirectionFromOffset(details.delta);
print('🔄 ドラッグ方向: $direction');
}
},
onPanEnd: (details) {
final velocity = details.velocity.pixelsPerSecond;
final speed = velocity.distance;
print('📈 フリック速度: ${speed.toStringAsFixed(2)} px/s');
if (speed > flickThreshold) {
final direction = _getDirectionFromOffset(velocity);
widget.flick.onFlick(direction);
} else if (isDragging) {
print('🛑 ロングタップ+スロー移動(ドラッグ)終了');
}
isDragging = false;
},
child: Transform.translate(
offset: _offset,
child: widget.flick.child,
),
);
}
Direction _getDirectionFromOffset(Offset offset) {
final dx = offset.dx;
final dy = offset.dy;
if (dx.abs() > dy.abs()) {
return dx > 0 ? Direction.right : Direction.left;
} else {
return dy > 0 ? Direction.down : Direction.up;
}
}
}
//-------------------- テスト用画像 -------------------------
class ImageBoxWidget extends StatelessWidget {
const ImageBoxWidget({super.key});
@override
Widget build(BuildContext context) {
return Image.network(
'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg',
width: 200,
height: 200,
fit: BoxFit.cover,
);
}
}