最近、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),
),
);
}
}
べた書きしているコードを、カスタムウィジットとして抽出します。
VSCodeでは書き出したいウィジットをクリック(カーソルがあればいい)>電球?マークをクリックで簡単にできます
android studioでは Flutter OutLineをクリック>書き出したいウィジットを右クリックで簡単にできます
カスタムウィジットと並べてみる
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('ボタンだよ'),
),
),
],
),
);
}
}
同じようなウィジットが並ぶレイアウトを作成したい場合、コードがさっぱりします。
このように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),
),
),
],
),
);
}
}
プロパティを指定できるようにしました。
これにより、同じウィジットを使って別の表現ができます。
今回は、この方法でいろいろ試してみました。
いろいろ試した結果
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)),
),
],
),
),
),
],
),
);
}
}
まだまだ、カスタマイズしたいところですが、今回はこれぐらいにしました!
最後まで見ていただいてありがとうございます!!!