Flutter:IconButtonで長押し処理を実装する方法

Flutter

IconButton(アイコンボタン)に、長押し処理の属性(プロパティ)が無いけど、どうやったら実装できるの?

という方向けのショート記事です。

 

簡単そうで、意外と手間取ってしまい、またネット上にもスバリの記事がなかったので、整理してみました。

 

自分なりに得た結論は、以下になります。

 

  • IconButtonを、InkWellまたはGestureDetectorでラップした上で、
  • 長押し処理は、InkWellまたはGestureDetectorに設定し、
  • タップ処理は、IconButtonに設定する

 

こちらの情報を参考にさせていただきました。

 

 

以降に、自作したサンプルコードを使ってご説明します。

 

なお、ツールチップと併用する方法については、以下の記事に整理しましたので、よろしければ、こちらもご参考ください。

 

 


 

40代からプログラミング(Flutter)を始めて、GooglePlayAppStoreにアプリを公開しているhalzo appdevです。

先日、2つ目のアプリを公開しました。全てFlutterで開発したアプリです。

 

作成したアプリはこちら↓

かんたんプリント管理:アラート・OCR文字認識・検索機能を搭載

Google Play で手に入れよう
Download on the App Store

 

シンプルメモ帳「BasicMemo」 – 文字カウント、ワンタッチ入力、タグ管理等の機能を搭載

Google Play で手に入れよう
Download on the App Store

 

スポンサーリンク

IconButtonとは

「IconButton」は、「icon」属性に「Icon」ウィジェットを設置し、「onPressed」属性にタップ処理を実装することで、デザイン的にも違和感のないアイコン付きボタンを作れるウェジェットです。

 

しかし、「TextButton」や「ElevatedButton」に用意されているような、長押し処理をするための属性(onLongPress属性やonLongTap属性など)がありません

 

これはIconButtonに、長押しすると、説明のポップアップを表示できる「tooltip」属性があり、これと競合するためと思われます(公式説明を探したのですが、明確な記述は見つかりませんでした。。)。

 

スポンサーリンク

作成したサンプルコードの全体像

以下に、作成したサンプルコードを掲載します。そのまま「main.dart」に貼り付ければ、挙動を確認いただけます。

 

左側のボタンが後述する「方法①」、右側のボタンが「方法②」で実装したものです。

 

どちらのボタンも、タップすると数字が1増え、長押しすると1減ります。

 

// クラス名、メソッド名、プロパティ名(変数名)について、筆者が作成したもの(名前変更可のもの)
// の名前の末尾には、大文字のオー「O」をつけています
// ※ライブラリ(パッケージ)で予め決められているもの(名前の変更不可のもの)と、
//  自分で作成したもの(名前の変更可のもの)の区別をしやすくするため

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Test",
      home: SampleScreenO(),
    );
  }
}

class SampleScreenO extends StatefulWidget {
  @override
  _SampleScreenOState createState() => _SampleScreenOState();
}

class _SampleScreenOState extends State<SampleScreenO> {

  // ここまではお決まりのコード

  // カウンター変数を定義
  int counterO = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[

            // 方法①:左ボタン
            // ↓は「GestureDetector(」でも可能
            InkWell(

              // 長押しした時は、カウンター変数を1減らし、全体を再描画
              onLongPress: () => setState(() {
                counterO--;
              }),

              // タップしたときは、カウンター変数を1増やし、全体を再描画
              onTap: () => setState(() {
                counterO++;
              }),

              // パディングを設定しないと、タップ可能範囲が狭くなるため設定
              child: Padding(
                padding: const EdgeInsets.all(8.0),

                // アイコン画像に、処理の設定は不要なため、Iconのみ(IconButtonは使わない)
                child: Icon(Icons.add),
              ),
            ),

            SizedBox(
              width: 30,
            ),

            // 方法②:右ボタン
            // ↓は「GestureDetector(」でも可能
            InkWell(

              // 長押し処理のみをInkWellに設定
              // 長押しした時は、カウンター変数を1減らし、全体を再描画
              onLongPress: () => setState(() {
                counterO--;
              }),

              // タップ処理はアイコン画像の方に設定するため、IconButtonを使用
              child: IconButton(
                icon: Icon(Icons.add),

                // タップした時は、カウンター変数を1増やし、全体を再描画
                onPressed: () => setState(() {
                  counterO++;
                }),
              ),
            ),
          ],
        ),
      ),

      body: Container(
        alignment: Alignment.topCenter,
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            children: [

              // カウンターを表示
              Text(
                "$counterO",
                style: TextStyle(fontSize: 20),
              ),

              // 画面範囲に収まるようにFittedBoxでラップ
              FittedBox(
                fit: BoxFit.scaleDown,
                child: Text(
                  "タップで +1、長押しで -1\n【左ボタン】InkWellにタップ・長押し両方をセット\n【右ボタン】IconButtonにタップ、InkWellに長押しをセット",
                  textAlign: TextAlign.center,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

サンプルコードの実行画面

 

スポンサーリンク

方法① IconをInkWellまたはGestureDetectorでラップ→タップ範囲が不自然

最初にこの方法を考えました(左側のボタンの方法です)。

  

「InkWell」や「GestureDetector」は、任意のウィジェットにタップ処理を追加できるウィジェットです。

 

「InkWell」は、タッチフィードバック(タップ時に反応を示すアニメーション)を付けたい時に用いますが、使用できる属性が「GestureDetector」に比べると少ないです。

 

いずれもタップ処理の属性(onTap)と長押し処理の属性(onLongPress)を持っているため、「IconButton」は使わず、「Icon」ウェジェットをラップして、処理は全て「InkWell」や「GestureDetector」に持たせることにします。

 

留意点は、「Icon」ウェジェットに「Padding」を付けておく点です。

 

「Padding」がないと、非常に狭い範囲しかタップ対象にならないため、使いづらいボタンになってしまいます。

 

 

ただ、「Padding」を付けたとしても、タップ範囲が矩形になるので、やや違和感があります。

 

スポンサーリンク

方法② IconButtonをInkWellまたはGestureDetectorでラップし、タップと長押しの担当を分ける

右側のボタンの方法です。

 

「IconButton」は、タップ範囲が円形で、タッチフィードバックも備えているので、その機能を活かしたいと思いました。

 

そこで、タップ処理は「IconButton」に持たせ、長押し処理だけ、ラップした「InkWell」または「GestureDetector」に持たせたところ、問題なく動作しました。

 

ラップしてしまうと、どちらかの処理が優先されるのではないかと思いましたが、大丈夫でした。

 

 

個人的には、タップ時の挙動が自然なので、こちらの方法の方がお勧めです。

 

スポンサーリンク

留意点:tooltipとは併用できない

「IconButton」には、「tooltip」属性があり、これを設定すると、長押し時にツールチップ(説明のポップアップ)を表示できます。

 

ただ、以下のように「tooltip」属性も設定してしまうと、ツールチップの表示が優先され、「InkWell」または「GestureDetector」の「onLongPress」属性が効かなくなってしまいます

 

InkWell(
              onLongPress: () => setState(() {
                counterO--;
              }),

              child: IconButton(
                icon: Icon(Icons.add),

                // ↓これを設定すると、InkWellの「onLongPress」が動作しない
                tooltip: "ツールチップを表示",

                onPressed: () => setState(() {
                  counterO++;
                }),
              ),
            ),

 

この点の解決方法については、以下の記事に整理したので、よろしければご参考にして下さい。

 

 

 

以上、ご参考になれば幸いです。

 

最後までお読みいただき、ありがとうございました。

 

\ Flutterの学習で役立ったコンテンツ・書籍 /

The Complete 2021 Flutter Development Bootcamp with Dart

 

 


Dart入門 – Dartの要点をつかむためのクイックツアー

コメント

タイトルとURLをコピーしました