Flutterでチップ計算機アプリを作る方法【初心者向けチュートリアル】
Flutterはモバイルアプリ開発フレームワークとしてますます人気が高まっており、多くの企業が自社プロジェクトに採用し始めています。
多くの開発者がFlutterを高く評価しているのは、シンプルなウィジェット構造を使ってピクセル単位で美しいUIを持つアプリを開発できる点です。UI開発の手軽さに加え、Dartプログラミング言語による機能ロジックの実装も可能なため、Flutterはモバイルアプリ開発の未来だと言えるでしょう。
このチュートリアルでは、シンプルなチップ計算機アプリを作りながら、Flutterフレームワークの基礎を学びます。Stateful(ステートフル)ウィジェットとStateless(ステートレス)ウィジェットといった標準的なコーディングパターンや、Flutterアプリ開発で最もよく使うウィジェットについても解説します。
まずはスターターのFlutterプロジェクトをセットアップし、その後、全体のUI実装と基本機能の実装へと進みます。それでは始めましょう!
Flutterプロジェクトのセットアップ方法
新しいFlutterプロジェクトを作成するには、システムにFlutter SDKがインストールされている必要があります。簡単かつ迅速にインストールするには、公式のFlutterドキュメントを参照してください。
Androidプラットフォーム向けにアプリを開発する場合は、Android StudioとAndroid SDKも必要になる点に注意してください。
ドキュメントに従ってすべてのセットアップが完了したら、ターミナルで以下のFlutterコマンドを実行します。
flutter create tipCalculator
このコマンドにより、スターターのFlutterプロジェクトが自動的にダウンロード・セットアップされます。次に、Visual Studio Codeでプロジェクトを開きましょう。
デバイスシミュレーターまたは実際のスマートフォンが接続されていれば、以下のコマンドでアプリを起動できます。
flutter run
あるいは、キーボードの「F5」キーを押すとVSCodeにメニューが表示され、そこからアプリを起動したいデバイスを選択できます。
このコマンドを正常に実行するには、拡張子が.dartのファイルを開いている必要がある点に注意してください。
上記のコマンドまたはF5キーでビルド・実行すると、エミュレーター/実機に以下のようなスターターテンプレートが表示されます。

これでFlutterアプリが起動できました。
ここで、メインファイルであるmain.dartの中身をもう少し詳しく見てみましょう。
main.dartファイルには2つのクラスオブジェクトがあります。一方はStatefulウィジェットを継承し、もう一方はStatelessウィジェットを継承しています。これはどういう意味なのでしょうか?
- Statefulウィジェット: アプリの状態(ステート)を保持するクラスです。状態は変化することがあり、変化するとこのクラス内のウィジェットが再描画されます。動的な状態変更に対応します。
- Statelessウィジェット: 状態を一切保持しないクラスです。変化しないウィジェットのビューを表現し、動的な状態変更には関与しません。
main.dartファイルにはmain()関数も含まれており、この関数内のrunAppメソッドでMyAppクラスを呼び出すことで、デバイス上でのFlutterアプリの起動がトリガーされます。
チップ計算機のUIを構築する方法
UIの実装を始めるために、まずMyHomePageStateクラス内にデフォルトで存在するコードをすべて削除します。
削除後、build関数からシンプルなScaffoldウィジェットを返すようにします。
Scaffoldウィジェットは、appBarやbodyを追加するためのプロパティを提供します。まずはシンプルなアプリバーを追加してみましょう。実装全体は以下のコードスニペットをご覧ください。
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:Text('Tip Calculator', style: TextStyle(color: Colors.black87),),
),
body: Container()
);
}
}
AppBarウィジェットのtitleプロパティでアプリバーを追加したら、ビルドして実行します。エミュレーター画面には以下のような結果が表示されるはずです。

Dartファイルを保存するとホットリロードが機能することに注目してください。プロジェクトのDartファイルに変更を加えて保存するたびに、変更内容が自動的にエミュレーターへ反映されます。
ステップ1:アプリバーをデザインする
ここでは、AppBarウィジェットが提供するさまざまなプロパティを使ってカスタマイズしていきます。変更後のコードは以下のスニペットの通りです。
appBar: AppBar(
title: Text('Tip Calculator', style: TextStyle(color: Colors.black87),),
centerTitle: true,
elevation: 0.0,
backgroundColor: Colors.white70,
),
ビルドして実行すると、エミュレーター画面に以下のような結果が表示されます。

ここでは、AppBarウィジェットの基本的なプロパティをいくつか使用しました。
elevation:z-indexのようにアプリバーの影の効果を制御できます。centerTitle:タイトルを中央揃えにします。- さらに背景色を白に変更しました。
ステップ2:Scaffoldのボディをデザインする
現時点では、bodyプロパティには空のContainerウィジェットしかありません。次に、以下のコードスニペットに従って、Containerウィジェットにプロパティと子ウィジェットを追加していきます。
body: Container(
color: Colors.white70,
padding: const EdgeInsets.all(16.0),
child: Center(
child: Form(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
],
),
),
),
ビルドして実行すると、エミュレーター画面に以下のような結果が表示されます。

上のスクリーンショットの通り、ボディの背景色を白に変更しました。また、paddingを追加し、子ウィジェットとしてCenterウィジェットを配置することで、UI全体をボディの中央に配置しています。
Centerウィジェットの子としてFormウィジェットがあり、ここにテキストフィールドを使ったシンプルなフォームを作成していきます。
そして最も重要なのは、Formの子ウィジェットとしてColumnウィジェットがあることです。Columnウィジェットはchildrenというウィジェット配列プロパティを提供しており、ここに任意の数のウィジェットを組み込むと、それらが画面上に縦方向に並んで表示されます。
ステップ3:定数と変数を定義する
フォーム要素(テキストフィールドなど)を実装する前に、入力フィールドからの値を扱うための定数をいくつか定義しておく必要があります。
必要な定数と変数は、以下のコードスニペットをご覧ください。
// This is the default bill amount
static const defaultBillAmount = 0.0;
// This is the default tip percentage
static const defaultTipPercentage = 15;
// This is the TextEditingController which is used to keep track of the change in bill amount
final _billAmountController =
TextEditingController(text: defaultBillAmount.toString());
// This is the TextEditingController which is used to keep track of the change in tip percentage
final _tipPercentageController =
TextEditingController(text: defaultTipPercentage.toString());
// This stores the latest value of bill amount calculated
double _billAmount = defaultBillAmount;
// This stores the latest value of tip percentage calculated
int _tipPercentage = defaultTipPercentage;
上記のコードでは、TextEditingControllerを使用しています。このコントローラーにより、後ほどTextFormFieldウィジェット内のテキスト入力を扱えるようになり、デフォルト値で初期化されます。
ステップ4:入力フォームフィールドを追加する
次に、TextFormFieldウィジェットを使って2つの入力フォームフィールドを追加します。
このウィジェットを使用する際は、先ほど定義したコントローラー変数をcontrollerプロパティに必ず割り当てる必要があります。ウィジェットの実装全体は以下のコードスニペットをご覧ください。
body: Container(
color: Colors.white70,
padding: const EdgeInsets.all(16.0),
child: Center(
child: Form(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
key: Key("billAmount"),
controller: _billAmountController,
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration: InputDecoration(
hintText: 'Enter the Bill Amount',
labelText: 'Bill Amount',
labelStyle: TextStyle(
fontSize: 25,
letterSpacing: 1,
fontWeight: FontWeight.bold
),
fillColor: Colors.white,
border: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(20.0),
),
),
),
SizedBox(height: 25,),
TextFormField(
key: Key("tipPercentage"),
controller: _tipPercentageController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: 'Enter the Tip Percentage',
labelText: 'Tip Percentage',
labelStyle: TextStyle(
fontSize: 25,
letterSpacing: 1,
fontWeight: FontWeight.bold
),
fillColor: Colors.white,
border: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(20.0),
),
),
),
],
),
),
),
),
ここではkeyboardTypeプロパティを設定しています。これにより、ユーザーが入力フィールドをタップした際に、適切な種類のキーボードが表示されるようになります。
また、decorationプロパティを使えば、InputDecorationウィジェットで入力フィールドをスタイリングできます。
InputDecorationウィジェットには複数のプロパティがあり、プレースホルダーテキストや入力欄上部のラベルを表示できます。さらにborderプロパティを適用して、角丸のアウトラインボーダーも表示させています。
ビルドして実行すると、エミュレーター画面に以下のような結果が表示されます。

ステップ5:イベントリスナーと関数を追加する
ユーザーが請求額やチップ率を入力した瞬間にチップ金額を計算したいので、テキスト入力フィールドの変更をリッスンする必要があります。
そのためには、addListenerメソッドを使ってコントローラーにイベントリスナーを追加します。
そして、入力フィールドに何らかの変更が発生したらすぐに、請求額とチップ率を更新する特定の関数をトリガーしたいところです。
そのために、setStateメソッドと組み合わせた必須の関数を使用します。setStateは、変更が発生した際にUI全体を再描画してくれる仕組みです。
setStateメソッドはbuildメソッドの再実行をトリガーする点に注意してください。
実装全体は以下のコードスニペットをご覧ください。
@override
void initState() {
super.initState();
_billAmountController.addListener(_onBillAmountChanged);
_tipPercentageController.addListener(_onTipAmountChanged);
}
_onBillAmountChanged() {
setState(() {
_billAmount = double.tryParse(_billAmountController.text) ?? 0.0;
});
}
_onTipAmountChanged() {
setState(() {
_tipPercentage = int.tryParse(_tipPercentageController.text) ?? 0;
});
}
ここではinitStateメソッドも使っています。このメソッドは、アプリでこの画面に入った直後に実行されるため、画面に入ると同時にイベントリスナーが登録される仕組みです。
ステップ6:金額セクションを追加する
再びUIウィジェットに戻ります。入力フィールドの下、Columnウィジェットの中に金額セクションを追加していきます。
ここでも、ウィジェット間に必要な余白を設けるためのSizedBoxウィジェットを使用します。金額セクションの実装は以下のコードスニペットをご覧ください。
),
SizedBox(height: 20,),
Container(
margin: EdgeInsets.all(15),
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(15),
),
border: Border.all(color: Colors.white),
boxShadow: [
BoxShadow(
color: Colors.black12,
offset: Offset(2, 2),
spreadRadius: 2,
blurRadius: 1,
),
],
),
child: Column(
children: [
Text("Tip Amount"),
Text("Total Amount")
],
),
),
ここでは、必要なスタイル装飾を施したContainerを配置しています。childプロパティには別のColumnウィジェットが入っており、2つのTextウィジェットが縦に並んでいます。
ビルドして実行すると、エミュレーター画面に以下のような結果が表示されます。

ステップ7:金額表示用の独立したStatelessウィジェットを作成する
チップ金額と合計金額には少しスタイルを加えて表示したいので、状態を保持しない代わりに、Statefulウィジェットから渡された値に依存する専用ウィジェットを作成します。
AmountTextというStatelessウィジェットクラスの実装全体は、以下のコードスニペットをご覧ください。
class AmountText extends StatelessWidget {
final String text;
const AmountText(
this.text, {
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8),
child: Text(text.toUpperCase(),
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blueAccent, fontSize: 20)),
);
}
}
ここではコンストラクタを使って、表示する実際のテキストを受け取っています。このクラスのbuildメソッドは、シンプルなpaddingを持つContainerウィジェットと、子ウィジェットとしてのTextウィジェットを返します。
AmountTextウィジェットの準備ができたので、Statefulウィジェット側からこのウィジェットを呼び出しましょう。
先ほどシンプルなTextウィジェットを並べたColumnウィジェットの中に、このウィジェットを組み込みます。TextウィジェットをAmountTextウィジェットに置き換え、必要なテキスト値を渡すだけです。
実装は以下のコードスニペットをご覧ください。
Container(
margin: EdgeInsets.all(15),
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(15),
),
border: Border.all(color: Colors.white),
boxShadow: [
BoxShadow(
color: Colors.black12,
offset: Offset(2, 2),
spreadRadius: 2,
blurRadius: 1,
),
],
),
child: Column(
children: [
AmountText(
'Tip Amount: ${_getTipAmount()}',
key: Key('tipAmount'),
),
AmountText(
'Total Amount: ${_getTotalAmount()}',
key: Key('totalAmount'),
),
],
),
),
ここでは、AmountTextウィジェットに関数を渡しています。この関数はそれぞれのチップ金額と合計金額の値を返します。詳細は以下のコードスニペットをご覧ください。
_getTipAmount() => _billAmount * _tipPercentage / 100;
_getTotalAmount() => _billAmount + _getTipAmount();
最後に、画面を離れる際にコントローラーを破棄する必要があります。そのために、組み込みのdispose関数を使用します。この関数は、現在の画面から退出するタイミングで実行されます。
このメソッド内で、各コントローラーのdisposeメソッドを呼び出し、入力テキストコントローラーを終了させます。これにより、コントローラーが入力フィールドの変更をリッスンするのを停止できます。
dispose関数は以下のコードスニペットをご覧ください。
@override
void dispose() {
// To make sure we are not leaking anything, dispose any used TextEditingController
// when this widget is cleared from memory.
_billAmountController.dispose();
_tipPercentageController.dispose();
super.dispose();
}
ビルドして実行すると、チップ計算機の実装の最終結果が得られます。デモは以下の通りです。

入力フィールドの値を変更すると、両方の金額表示が連動して変わることがわかります。
以上でこのチュートリアルは終了です。FlutterフレームワークとDartを使って、シンプルなチップ計算機を実装できましたね。
次のステップへ進むには?
このチュートリアルの主な目的は、シンプルなチップ計算機を作りながら、Flutterアプリ開発フレームワークにおける基本的なプログラミングパターンを学ぶことでした。
まだまだ興味深いウィジェットや機能がたくさんあります。機能コンポーネントはそのままに、アプリのUIだけをアレンジしてみるのも良いでしょう。
全体として、Flutterはウィジェットパターンによって複雑なUIデザインをシンプルにしてくれます。childプロパティを使ってウィジェットを積み重ねるだけで、素晴らしいUIを作成できます。
次のステップとしては、Flutterのナビゲーション機構を使って異なる画面間を移動できるようにするのがおすすめです。カスタムドロワーメニューやボトムタブの追加も、Flutterなら簡単に行えます。
これはFlutter開発のほんの出発点にすぎません。表面からは想像できないほど奥が深い世界です。探求とコーディングを続けていきましょう。
プロジェクト全体のデモはCodepenで公開されています。
すでに公開されている他のFlutterアプリからインスピレーションを得るのも良いアイデアです。
Happy Coding!
-
Windows PCでGrammarlyを使う方法|デスクトップアプリとブラウザ拡張機能の導入ガイド
日々の仕事に何らかの文章作成が含まれているなら、文法やスペル、表現の明瞭さなどをチェックしてくれるタイピングアシスタントアプリは、もはや必需品と言っても過言ではありません。 Grammarly(グラムリー)は、そのような文章作成アシスタントとして世界的に人気の高いツールの一つです。Windows向けには、ブラウザ拡張機能とWindowsアプリの両方が提供されており、用途に応じて使い分けることができます。この記事では、それぞれの方法でGrammarlyをWindows上で活用する手順を詳しく見ていきましょう。 WindowsでGrammarlyを使用する2つの方法 Windowsユーザーにとって
-
PCでTelegramを使う方法|デスクトップアプリとWeb版の導入手順を徹底解説
Telegramは、プライベートメッセージングの世界において、堅牢なセキュリティを誇る数少ないアプリの一つとして高い評価を得ています。電話番号ではなくユーザー名で新しい相手とつながれる仕組みを採用しているため、プライバシーを重視するユーザーの間で人気の選択肢となっています。あらゆる安全な通信の決定打というわけではありませんが、自分のやり取りを目立たない状態に保つうえで大きな助けになります。 ユーザー数が着実に増え続ける中、その一部がPCからTelegramへアクセスしたいと考えるのは自然な流れです。幸い、他の人気メッセージングアプリと同様に、TelegramもPCにインストールして快適に利用で