4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Flutter練習帳】GestureDetectorをつかってボタンを作る

Last updated at Posted at 2023-06-09

最近、Flutterを始めました。

練習と備忘録をかねて記事にまとめています。

実際に動かしてみたいけどFlutterの開発環境がない人は、
https://dartpad.dev/
にペタっとコピペして試してみてください。

結果だけ興味があるかたは、下までスクロールしてください!

今回は、GestureDetectorを使って、ボタンを作る練習と、
カスタムウィジット(クラス)をつくる練習をしています。

ボタンやったらButton系のクラスを使えばええやん?
そう思いますが、そうはいかないときや、ボタンではないものをクリックしたいとき、
タップ以外の操作でもイベントを起こしたいときにGestureDetectorは使えます。

サンプルコード 基本ボタン
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter button custom',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter button custom'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        // 初期のコード
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            // 区切り
            const SizedBox(
              height: 10,
            ),

            GestureDetector(
              onTap: () {
                // 実行したい処理を入れる
                print('tap');
              },
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Align(
                    alignment: Alignment.center,
                    child: Container(
                      padding: const EdgeInsets.all(12),
                      height: 50,
                      width: 100,
                      decoration: BoxDecoration(
                        color: Colors.blue,
                        border: Border.all(color: Colors.red),
                        borderRadius: BorderRadius.circular(10),
                      ),
                      child: const Text('ボタンだよ'),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), 
    );
  }
}

シンプルですね
image.png

べた書きしているコードを、カスタムウィジットとして抽出します。

VSCodeでは書き出したいウィジットをクリック(カーソルがあればいい)>電球?マークをクリックで簡単にできます
image.png

android studioでは Flutter OutLineをクリック>書き出したいウィジットを右クリックで簡単にできます
image.png

カスタムウィジットと並べてみる
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter button custom',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter button custom'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        // 初期のコード
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            // 区切り
            const SizedBox(
              height: 10,
            ),

            GestureDetector(
              onTap: () {
                // 実行したい処理を入れる
                print('tap');
              },
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Align(
                    alignment: Alignment.center,
                    child: Container(
                      padding: const EdgeInsets.all(12),
                      height: 50,
                      width: 100,
                      decoration: BoxDecoration(
                        color: Colors.blue,
                        border: Border.all(color: Colors.red),
                        borderRadius: BorderRadius.circular(10),
                      ),
                      child: const Text('ボタンだよ'),
                    ),
                  ),
                ],
              ),
            ),

            // 区切り
            const SizedBox(
              height: 10,
            ),

            // カスタムウィジットとして切り出し
            const CustomButton(),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), 
    );
  }
}

// 切り出したカスタムウィジット
class CustomButton extends StatelessWidget {
  const CustomButton({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        // 実行したい処理を入れる
        print('カスタム');
      },
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Align(
            alignment: Alignment.center,
            child: Container(
              padding: const EdgeInsets.all(12),
              height: 50,
              width: 100,
              decoration: BoxDecoration(
                color: Colors.blue,
                border: Border.all(color: Colors.red),
                borderRadius: BorderRadius.circular(10),
              ),
              child: const Text('ボタンだよ'),
            ),
          ),
        ],
      ),
    );
  }
}

image.png

同じようなウィジットが並ぶレイアウトを作成したい場合、コードがさっぱりします。
このようにFlutterでは、ウィジットを駆使して画面を作成していきます。

さらに汎用的なカスタムウィジット
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter button custom',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter button custom'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        // 初期のコード
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            // 区切り
            const SizedBox(
              height: 10,
            ),

            GestureDetector(
              onTap: () {
                // 実行したい処理を入れる
                print('tap');
              },
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Align(
                    alignment: Alignment.center,
                    child: Container(
                      padding: const EdgeInsets.all(12),
                      height: 50,
                      width: 100,
                      decoration: BoxDecoration(
                        color: Colors.blue,
                        border: Border.all(color: Colors.red),
                        borderRadius: BorderRadius.circular(10),
                      ),
                      child: const Text('ボタンだよ'),
                    ),
                  ),
                ],
              ),
            ),

            // 区切り
            const SizedBox(
              height: 10,
            ),

            // カスタムウィジットとして切り出し
            const CustomButton(),

            // 区切り
            const SizedBox(
              height: 10,
            ),

            // パラメータを使用したカスタムウィジット
            const CustomButton2(
              text: 'ボタンだよ',
              borderColor: Colors.red,
              backgroundColor: Colors.blue,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

// 切り出したカスタムウィジット
class CustomButton extends StatelessWidget {
  const CustomButton({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        // 実行したい処理を入れる
        print('カスタム');
      },
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Align(
            alignment: Alignment.center,
            child: Container(
              padding: const EdgeInsets.all(12),
              height: 50,
              width: 100,
              decoration: BoxDecoration(
                color: Colors.blue,
                border: Border.all(color: Colors.red),
                borderRadius: BorderRadius.circular(10),
              ),
              child: const Text('ボタンだよ'),
            ),
          ),
        ],
      ),
    );
  }
}

// パラメータを使用したカスタムウィジット
class CustomButton2 extends StatelessWidget {
  // プロパティ
  final String text;
  final Color backgroundColor;
  final Color borderColor;

  const CustomButton2({
    super.key,
    // コンストラクタ
    required this.text,
    required this.backgroundColor,
    required this.borderColor,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        // 実行したい処理を入れる
        print('プロパティ');
      },
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Align(
            alignment: Alignment.center,
            child: Container(
              padding: const EdgeInsets.all(12),
              height: 50,
              width: 100,
              decoration: BoxDecoration(
                color: backgroundColor,
                border: Border.all(color: borderColor),
                borderRadius: BorderRadius.circular(10),
              ),
              child: Text(text),
            ),
          ),
        ],
      ),
    );
  }
}

image.png

プロパティを指定できるようにしました。
これにより、同じウィジットを使って別の表現ができます。

今回は、この方法でいろいろ試してみました。

いろいろ試した結果
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter button custom',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter button custom'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
        padding: const EdgeInsets.all(20),
        child: Center(
          // 初期のコード
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text(
                'You have pushed the button this many times:',
              ),
              Text(
                '$_counter',
                style: Theme.of(context).textTheme.headlineMedium,
              ),
              // 区切り
              const SizedBox(
                height: 10,
              ),

              GestureDetector(
                onTap: () {
                  // 実行したい処理を入れる
                  print('tap');
                },
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Align(
                      alignment: Alignment.center,
                      child: Container(
                        padding: const EdgeInsets.all(12),
                        height: 50,
                        width: 100,
                        decoration: BoxDecoration(
                          color: Colors.blue,
                          border: Border.all(color: Colors.red),
                          borderRadius: BorderRadius.circular(10),
                        ),
                        child: const Text('ボタンだよ'),
                      ),
                    ),
                  ],
                ),
              ),

              // 区切り
              const SizedBox(
                height: 10,
              ),

              // プロパティを反映したカスタムウィジット
              CustomButton2(
                text: 'ボタンだよ',
                borderColor: Colors.blue, 
                backgroundColor: Colors.white, 
                onTap: () {
                  print('aの処理をする');
                },
                icon: const Icon(Icons.insert_chart),
                subText: "ぼたんらしいよ",
                isHot: false,
                shadow: true,
                gradation: false,
              ),

              // 区切り
              const SizedBox(
                height: 10,
              ),

              // プロパティを反映したカスタムウィジット
              CustomButton2(
                text: 'ボタンだよ',
                borderColor: Colors.blue, 
                backgroundColor: Colors.white, 
                onTap: () {
                  print('bの処理をする');
                },
                icon: const Icon(Icons.pets),
                subText: "ぼたんらしいよ",
                isHot: true,
                shadow: false,
                gradation: true,
                backgroundColorSecond: Colors.indigo,
              ),

              // 区切り
              const SizedBox(
                height: 10,
              ),

              // プロパティを反映したカスタムウィジット
              CustomButton2(
                text: 'ボタンだよ',
                borderColor: Colors.white,
                backgroundColor: Colors.blue,
                onTap: () {
                  print('cの処理をする');
                },
                icon: const Icon(Icons.abc),
                subText: "ぼたんらしいよ",
                isHot: false,
                shadow: false,
                gradation: false,
              ),

              // 区切り
              const SizedBox(
                height: 10,
              ),

              // プロパティを反映したカスタムウィジット
              CustomButton2(
                text: 'ボタンだよ', 
                borderColor: Colors.white,
                backgroundColor: Colors.blue, 
                onTap: () {
                  print('dの処理をする');
                },
                icon: const Icon(Icons.create),
                subText: "ぼたんらしいよ",
                isHot: false,
                shadow: false,
                gradation: true,
                backgroundColorSecond: Colors.yellow,
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

// プロパティを使用したカスタムウィジット
class CustomButton2 extends StatelessWidget {
  // プロパティ
  final String text; 
  final Color backgroundColor; 
  final Color borderColor; 
  final VoidCallback? onTap;
  final Icon icon;
  final String subText;
  final bool isHot;
  final bool shadow;
  final bool gradation;
  final Color? backgroundColorSecond;

  const CustomButton2({
    super.key,
    // コンストラクタ
    required this.text,
    required this.backgroundColor,
    required this.borderColor,
    this.onTap,
    required this.icon,
    required this.subText,
    required this.isHot,
    required this.shadow,
    required this.gradation,
    this.backgroundColorSecond,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Align(
            alignment: Alignment.center,
            child: Container(
              padding: const EdgeInsets.all(12),
              decoration: shadow
                  ? BoxDecoration(
                      color: backgroundColor,
                      border: Border.all(color: borderColor), 
                      borderRadius: BorderRadius.circular(50),
                      boxShadow: const [
                        BoxShadow(blurRadius: 5, color: Colors.grey),
                      ],
                    )
                  : gradation
                      ? BoxDecoration(
                          gradient: LinearGradient(
                              begin: Alignment.topLeft,
                              end: Alignment.bottomRight,
                              colors: [
                                backgroundColor,
                                backgroundColorSecond!,
                              ]), 

                          border: Border.all(color: borderColor),
                          borderRadius: BorderRadius.circular(50),
                        )
                      : BoxDecoration(
                          color: backgroundColor, 
                          border: Border.all(color: borderColor), 
                          borderRadius: BorderRadius.circular(50),
                        ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          SizedBox(
                            child: Center(child: icon),
                          ),
                          Text(text),
                          Visibility(
                              visible: isHot,
                              child: const Icon(Icons.whatshot_outlined))
                        ],
                      ),
                      Text(subText),
                    ],
                  ),
                  const SizedBox(
                    child: Center(child: Icon(Icons.arrow_forward_outlined)),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}


いろいろとイケてないところは、気にしないでください。
image.png

まだまだ、カスタマイズしたいところですが、今回はこれぐらいにしました!

最後まで見ていただいてありがとうございます!!!

4
2
0

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
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?