Skip to content

Instantly share code, notes, and snippets.

@riscait
Last active March 30, 2021 00:37
Show Gist options
  • Save riscait/6f930033ab2940beaa006f8ae4689e40 to your computer and use it in GitHub Desktop.
Save riscait/6f930033ab2940beaa006f8ae4689e40 to your computer and use it in GitHub Desktop.
Awaitable Button
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: Text('Awaitable Button')),
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(16),
child: AwaitableButton(
onPressed: () async {
await Future.delayed(const Duration(seconds: 3));
},
textLabel: 'START',
executingLabel: 'Loading...'
),
),
),
);
}
}
/// 連打防止のボタン
/// [onPressed]に非同期な関数を設定すると、実行中はインジケータを表示し押せなくなる
@immutable
class AwaitableButton extends StatefulWidget {
const AwaitableButton({
Key key,
@required this.onPressed,
@required this.textLabel,
this.executingLabel,
this.height = 44,
this.buttonStyle,
}) : super(key: key);
/// 押した時の動作
final VoidCallback onPressed;
/// ボタンに表示するラベル文字列
final String textLabel;
/// 実行中に表示する文字列。nullなら表示しない
final String executingLabel;
/// ボタンのスタイル設定。任意設定
final ButtonStyle buttonStyle;
/// ボタンの高さ。defaultでは[44]
final double height;
@override
_AwaitableButtonState createState() => _AwaitableButtonState();
}
class _AwaitableButtonState extends State<AwaitableButton> {
var _isExecuting = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _onPressed,
child: ElevatedButton(
onPressed: widget.onPressed == null || _isExecuting ? null : _onPressed,
style: widget.buttonStyle ??
ButtonStyle(
foregroundColor: MaterialStateProperty.all(
Theme.of(context).colorScheme.onPrimary),
backgroundColor: MaterialStateProperty.all(
Theme.of(context).colorScheme.primary),
elevation: MaterialStateProperty.all(0),
minimumSize: MaterialStateProperty.all(Size(
double.infinity,
widget.height,
)),
),
child: AnimatedSwitcher(
child: _isExecuting
? widget.executingLabel == null
? _Indicator(size: widget.height / 2)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_Indicator(size: widget.height / 2),
const SizedBox(width: 8),
Text(widget.executingLabel),
],
)
: Text(widget.textLabel),
duration: const Duration(milliseconds: 500),
),
),
);
}
Future<void> _onPressed() async {
if (_isExecuting) {
// do nothing.
return;
}
setState(() => _isExecuting = true);
await Future(() => widget.onPressed?.call());
setState(() => _isExecuting = false);
}
}
/// 実行中に表示するインジケータ
class _Indicator extends StatelessWidget {
const _Indicator({
Key key,
@required this.size,
}) : super(key: key);
final double size;
@override
Widget build(BuildContext context) {
return SizedBox(
width: size,
height: size,
child: const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.white),
strokeWidth: 2,
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment