LoginSignup
3
1

Flutterのタップイベントを同時に複数発火する

Last updated at Posted at 2023-08-29

結論

FlutterのGestureDetectorは1回のタップで1つのイベントしか発火されないようになっているため、RawGestureDetectorを使う必要があります。

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

class TitleScreen extends HookWidget {
  const TitleScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _GetAllScreenTapEventWidget(
        child: Center(
          child: SizedBox(
            width: 100,
            height: 100,
            child: GestureDetector(
              onTap: () => print("button tap event"),
              child: const Card(
                color: Colors.red,
                child: Text('Tap me'),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _GetAllScreenTapEventWidget extends HookWidget {
  final Widget child;
  const _GetAllScreenTapEventWidget({
    required this.child,
  });

  @override
  Widget build(BuildContext context) {
    void onTapDown(details) {
      print("all screen tap event");
    }

    return Stack(
      fit: StackFit.expand,
      children: [
        child,
        RawGestureDetector(
          behavior: HitTestBehavior.translucent,
          gestures: <Type, GestureRecognizerFactory>{
            _TapAnimationGestureRecognizer:
                GestureRecognizerFactoryWithHandlers<
                    _TapAnimationGestureRecognizer>(
              () {
                return _TapAnimationGestureRecognizer(onTapDown: onTapDown);
              },
              (_) {},
            ),
          },
        ),
      ],
    );
  }
}

class _TapAnimationGestureRecognizer extends OneSequenceGestureRecognizer {
  _TapAnimationGestureRecognizer({required this.onTapDown});
  final Function(PointerEvent) onTapDown;

  bool _isDown = false;

  @override
  void addAllowedPointer(PointerDownEvent event) {
    startTrackingPointer(event.pointer);
    // イベントを他のオブジェクトに伝搬させるためにrejectする
    resolve(GestureDisposition.rejected);
  }

  @override
  String get debugDescription => 'recognizer for duplicate tap down event';

  @override
  void didStopTrackingLastPointer(int pointer) {}

  @override
  void handleEvent(PointerEvent event) {
    if (event.down) {
      if (!_isDown) {
        onTapDown(event);
        _isDown = true;
      }
    } else {
      _isDown = false;
    }
  }
}

動作結果

上記のコードを実行すると以下のような画面が出ます。
Screenshot_20230829-223335.png

Tap meの領域を押すと、2つのタップイベントが発生して以下のようなログとなります。
スクリーンショット 2023-08-29 223550.png

Tap meの領域外を押すと、片方のタップイベントのみが発生して以下のようなログになります。
スクリーンショット 2023-08-29 223622.png

用途

ゲームアプリ等で良くある、タップした場所が光るアニメーションを実装するために作成しました。
他のボタンがある場所を押した場合でも、タップアニメーションを表示するためにイベントを拾う必要があります。
サンプルコードのようなコードを活用して、以下のようなタップアニメーションを作りました。
無題の動画 ‐ Clipchampで作成.gif

ホシヅクリというボードゲームのアプリ版で、以下にて配信中です。

iOS版
https://apps.apple.com/jp/app/%E3%83%9B%E3%82%B7%E3%83%85%E3%82%AF%E3%83%AA/id6449194423
Android版
https://play.google.com/store/apps/details?id=org.silverroom.tellus&pli=1

※執筆時点ではまだタップのアニメーションはリリースされていませんが、次回リリースに含まれる予定です。

参考記事

Flutterのタップイベントについてわかりやすく解説されているので、何故タップイベントが1つしか発火しないか気になる方はこちらの記事を見ることをおすすめします。
https://qiita.com/mjhd/items/8e300238e494d8755b44

3
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1