Badge(バッヂ)の実装
はじめに
Flutter 3.7になり、Badge(バッヂ)のWidgetが追加されました。 それまでは独自で作ったり、サードパーティパッケージで実装してました。
クラス定義
クラス定義は下記のようになっています。
Badgeに表示する内容はlabel引数にWidgetで指定するようです。
その他、 ・背景色や文字色などの色指定 ・サイズ指定(labelを指定しなかった場合に適用される) などができるようになっています。
Badge Badge({ Key? key, Color? backgroundColor, Color? textColor, double? smallSize, double? largeSize, TextStyle? textStyle, EdgeInsetsGeometry? padding, AlignmentDirectional? alignment, Widget? label, bool isLabelVisible = true, Widget? child, })
実装
通常のバッヂ(labelのみ指定)
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: 'バッヂサンプル', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({Key? key}): super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('バッヂサンプル'), ), body: const Center( child: Badge( label: Text('1'), ), ), ); } }
アイコン上にバッヂ表示(labelとchild指定)
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: 'バッヂサンプル', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({Key? key}): super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('バッヂサンプル'), ), body: const Center( child: Badge( label: Text('1'), child: Icon(Icons.notifications) ), ), ); } }
最後に
詳しくは公式ドキュメントを参照
色を選択するピッカーを実装する
はじめに
ToDOなどをカテゴリに分類することがあります。 カテゴリは名前だけでなく、色を指定することで、一目でどのカテゴリであるかわかりやすくしたいです。 色を指定するカラーピッカー機能を作るために自作でウィジェットを作ることを考えましたが手間がかかりそう。 その機能を実現してくれるパッケージを探したところ、flutter_colorpickerというパッケージが見つかったので、試してみました。
例
MaterialPicker
BlockPicker
MultipleChoiceBlockPicker
その他、様々な表示が用意されています。 下記は公式ページに記載されていたものです。
実装
- パッケージの導入
Terminalから追加する場合
# 下記をターミナルで実行 flutter pub add flutter_colorpicker
pubspec.ymlに直接追記する場合
# 下記をymlに追加し、pub getする dependencies: flutter_colorpicker: ^1.0.3
- import文の追加
ピッカーを表示したい画面のWidgetに下記を追記
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
- ピッカー表示処理の実装 例として、TextButtonタップ時のコードを記載しています。 BlockPickerを使い、色をひとつ選択します。
TextButton( onPressed: () { showDialog( builder: (context) => AlertDialog( title: const Text('色を選択'), content: SingleChildScrollView( child: BlockPicker( pickerColor: Colors.red, onColorChanged: (color) { // TODO: 変更時処理 }, ), ), ), context: context); }, child: const Text('カラーピッカーを表示する'), ),
実行
「カラーピッカーを表示する」というボタンをタップすると下記のピッカーが表示されます。
最後に
色を選択し、Colorクラスを得ることができました。 次はこれを永続化したいと思います。 Colorクラスをそのまま保存することはできないので、何らかの変換が必要となりそうです。 実装したらその記事を書きたいと思います。
SnackBarでメッセージ表示する
はじめに
データの保存/修正/削除などの処理の成功・失敗をユーザーに表示する。 SnackBarを使うのはその方法のひとつ。
SnackBarとは下記画像のような画面下でメッセージを表示するものです。
Scaffoldの中でSnackBarを表示することができます。 実装方法は
実装
下記は「タップ」ボタンをタップすると「エラーが発生しました」というSnackBarが表示されます
選択メニューの実装
はじめに
入力フォーム画面で使われることが多い選択メニューの実装方法です。
DropdownButtonとDropdownButtonFormFieldを使う2つの方法があります。 違いは表示を見ればわかると思いますが、DropdownButtonFormFieldはTextFieldのような表示がされます。 入力フォーム画面でTextFiledが多く使われている場合、DropdownButtonFormFieldを使ったほうがデザイン上の違和感がないです。
実装
DropdownButton
See the Pen Untitled by fjsw (@fjsw) on CodePen.
DropdownButtonFormField
GridViewで変な余白ができてレイアウトが崩れる
やりたいこと
GridViewに4つのバナー画像を画面幅いっぱいに2行2列で表示したい。
下記画像のような想定です。
プレースホルダーのバナー画像は下記のサイトを使って取得しています。 Placeholder.com: Placeholder.com – The Free Image Placeholder Service Favoured By Designers
問題
軽く実装してみたところバナーの上下に想定外の余白ができてしまいました。 Imageウィジェットの高さの計算がうまくいってないようです。
実装
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'サンプルApp', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'サンプル'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { static const imageUrls = [ 'https://via.placeholder.com/350x80/0000FF?Text=Banner01', 'https://via.placeholder.com/350x80/FF0000?Text=Banner02', 'https://via.placeholder.com/350x80/FFFF00?Text=Banner03', 'https://via.placeholder.com/350x80/000000?Text=Banner04', ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Container( child: Container( child: GridView.count( crossAxisCount: 2, children: [for (final imageUrl in imageUrls) makeBanner(imageUrl)], )), )); } Widget makeBanner(String imageUrl) { return Image.network(imageUrl); } }
対応
GridViewのchildAspectRatioはデフォルトで「1.0」が設定されていました。
GridView.count({ Key? key, Axis scrollDirection = Axis.vertical, bool reverse = false, ScrollController? controller, bool? primary, ScrollPhysics? physics, bool shrinkWrap = false, EdgeInsetsGeometry? padding, required int crossAxisCount, double mainAxisSpacing = 0.0, double crossAxisSpacing = 0.0, double childAspectRatio = 1.0, bool addAutomaticKeepAlives = true, bool addRepaintBoundaries = true, bool addSemanticIndexes = true, double? cacheExtent, List<Widget> children = const <Widget>[], int? semanticChildCount, DragStartBehavior dragStartBehavior = DragStartBehavior.start, ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, String? restorationId, Clip clipBehavior = Clip.hardEdge, })
バナーの幅と高さが同じになるようにウィジェットのサイズが計算されているようです。 対応方法としてはGridViewのchildAspectRatioにバナー画像のアスペクト比を設定したところ、想定していた通りの表示になりました。
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'サンプルApp', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'サンプル'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { static const imageUrls = [ 'https://via.placeholder.com/350x80/0000FF?Text=Banner01', 'https://via.placeholder.com/350x80/FF0000?Text=Banner02', 'https://via.placeholder.com/350x80/FFFF00?Text=Banner03', 'https://via.placeholder.com/350x80/000000?Text=Banner04', ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Container( child: Container( child: GridView.count( crossAxisCount: 2, childAspectRatio: 350/80, children: [for (final imageUrl in imageUrls) makeBanner(imageUrl)], )), )); } Widget makeBanner(String imageUrl) { return Image.network( imageUrl, ); } }