はじめに
Flutter入門としてメモアプリを作成したので記事にしてみました。
Flutterはこんなものだよ!という感じで簡単にまとめているので、
これから始めてみたいという方や初心者の方の参考になればと思います。
アプリ概要
メモリスト画面(TOP画面)
ボタン / アイコン | できること |
---|---|
+ | メモ追加画面へ遷移 |
削除(ゴミ箱) | メモを削除する |
編集(えんぴつ) | メモ編集画面へ遷移 |
メモ追加画面
ボタン | できること |
---|---|
Save | メモを追加し、メモリスト画面に戻る |
Cancel | メモの追加をキャンセルし、メモリスト画面に戻る |
メモ編集画面
ボタン | できること |
---|---|
Edit | メモを更新し、メモリスト画面に戻る |
Cancel | メモの更新をキャンセルし、メモリスト画面に戻る |
アプリ実装
Flutterプロジェクトを作成した際に作られる main.dart
ファイル に書き込みます。
共通処理
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
void main() {
runApp(MyMemoApp());
}
class MyMemoApp extends StatelessWidget {
// テーマカラーをブラウンにするために設定
static Map<int, Color> color = {
50: Color(0xFFEFEBE9),
100: Color(0xFFD7CCC8),
200: Color(0xFFBCAAA4),
300: Color(0xFFA1887F),
400: Color(0xFF8D6E63),
500: Color(0xFF895548),
600: Color(0xFF6D4C41),
700: Color(0xFF5D4037),
800: Color(0xFF4E342E),
900: Color(0xFF3E2723),
};
final MaterialColor primeColor = MaterialColor(0xFFA1887F, color);
@override
Widget build(BuildContext context) {
return MaterialApp(
// アプリ名
title: "Memo App",
theme: ThemeData(
// テーマカラー設定
primarySwatch: primeColor,
accentColor: primeColor
),
// メモリスト画面を表示
home: MemoListPage(),
);
}
}
1. メモリスト画面
- メモリストWidget
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('MemoList'),
),
// データを元にListViewを作成
body: ListView.builder(
itemCount: memoList.length,
itemBuilder: (context, index) {
// インデックスに対応するメモを取得する
return Slidable(
// 右方向にリストアイテムをスライドした場合の設定
startActionPane: ActionPane(
...
children: [
SlidableAction(
onPressed: (context) async {
// スライドして表示された編集ボタンを押下した際の動き
...
},
backgroundColor: Colors.brown,
icon: Icons.edit,
),
],
),
child: Card(
child: ListTile(
title: Text(memoList[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
iconSize: 20,
onPressed: () async {
// リストの削除ボタンを押下した際の処理
...
},
),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
// +ボタンを押下した際の処理
// メモ追加画面で追加した際の値をリストへ追加する
...
},
child: Icon(Icons.add),
),
);
}
コード詳細
// メモリスト画面用Widget
class MemoListPage extends StatefulWidget {
@override
_MemoListPageState createState() => _MemoListPageState();
}
class _MemoListPageState extends State<MemoListPage> {
// メモデータ
List<String> memoList = [];
@override
void initState() {
super.initState();
init();
}
// アプリ起動時に保存したデータを読み込む
void init() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
memoList = prefs.getStringList("memoList")!;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
// AppBarを表示し、タイトルも設定
appBar: AppBar(
title: Text("MemoList"),
),
// データを元にListViewを作成
body: ListView.builder(
itemCount: memoList.length,
itemBuilder: (context, index) {
// インデックスに対応するメモを取得する
return Slidable(
// 右方向にリストアイテムをスライドした場合の設定
startActionPane: ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.25,
children: [
SlidableAction(
onPressed: (context) async {
final prefs = await SharedPreferences.getInstance();
// "push"で新規画面に遷移
// メモ編集画面から渡される値を受け取る
final editText = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) => MemoEditPage(memoList[index])),
);
if (editText != null) {
// キャンセルした場合は、newListText が null となるため注意
setState(() {
// リスト追加
memoList[index] = editText;
});
}
prefs.setStringList("memoList", memoList);
},
backgroundColor: Colors.brown,
icon: Icons.edit,
),
],
),
child: Card(
child: ListTile(
title: Text(memoList[index]),
trailing: IconButton(
icon: Icon(Icons.delete),
iconSize: 20,
onPressed: () async {
final prefs = await SharedPreferences.getInstance();
setState(() {
memoList.removeAt(index);
prefs.setStringList("memoList", memoList);
});
},
),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
final prefs = await SharedPreferences.getInstance();
// "push"で新規画面に遷移
// メモ追加画面から渡される値を受け取る
final newListText = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
// 遷移先の画面としてメモ追加画面を指定
return MemoAddPage();
}),
);
if (newListText != null) {
// キャンセルした場合は、newListText が null となるため注意
setState(() {
// リスト追加
memoList.add(newListText);
});
}
prefs.setStringList("memoList", memoList);
},
child: Icon(Icons.add),
),
);
}
}
2. メモ追加画面
- メモ追加Widget
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('MemoAdd'),
),
body: Container(
...
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// 入力されたテキストを表示
Text(_text, style: TextStyle(color: Colors.brown[300])),
const SizedBox(height: 8),
// テキスト入力箇所
TextField(
onChanged: (String value) {
setState(() {
// データを変更したことを知らせる(画面を更新する)
_text = value;
});
},
),
const SizedBox(height: 8),
Container(
...
// Saveボタン
child: ElevatedButton(
onPressed: () {
// ボタン押下で前の画面に戻り、前の画面にデータを渡す
Navigator.of(context).pop(_text);
},
child: Text('Save', style: TextStyle(color: Colors.white)),
),
),
const SizedBox(height: 8),
Container(
...
// Cancelボタン
child: TextButton(
onPressed: () {
// ボタンを押下で前の画面に戻る
Navigator.of(context).pop();
},
child: Text('Cancel'),
),
),
],
),
),
);
}
コード詳細
// メモリスト追加画面用Widget
class MemoAddPage extends StatefulWidget {
@override
_MemoAddPageState createState() => _MemoAddPageState();
}
class _MemoAddPageState extends State<MemoAddPage> {
// 入力されたテキストをデータとして持つ
String _text = "";
// データを元に表示するWidget
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("MemoAdd"),
),
body: Container(
// 余白を付ける
padding: EdgeInsets.all(64),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// 入力されたテキストを表示
Text(_text, style: TextStyle(color: Colors.brown[300])),
const SizedBox(height: 8),
// テキスト入力箇所
TextField(
// 入力されたテキストの値を受け取る(valueが入力されたテキスト)
onChanged: (String value) {
// データを変更したことを知らせる(画面を更新する)
setState(() {
// データを変更
_text = value;
});
},
),
const SizedBox(height: 8),
Container(
// 横幅いっぱいに広げる
width: double.infinity,
// Saveボタン
child: ElevatedButton(
onPressed: () {
// "pop"で前の画面に戻る
// "pop"の引数から前の画面にデータを渡す
Navigator.of(context).pop(_text);
},
child: Text("Save", style: TextStyle(color: Colors.white)),
),
),
const SizedBox(height: 8),
Container(
// 横幅いっぱいに広げる
width: double.infinity,
// Cancelボタン
child: TextButton(
// ボタンをクリックした時の処理
onPressed: () {
// "pop"で前の画面に戻る
Navigator.of(context).pop();
},
child: Text("Cancel"),
),
),
],
),
),
);
}
}
3. メモ編集画面
- メモ編集Widget
@override
Widget build(BuildContext context) {
// 初期値を設定する
final controller = TextEditingController(text: _text);
// TextEditingControllerに設定したテキストのlengthをカーソルの位置に設定する
controller.selection = TextSelection.fromPosition(
TextPosition(offset: controller.text.length),
);
return Scaffold(
appBar: AppBar(
title: Text('MemoEdit'),
),
body: Container(
// メモ追加画面と同じ処理のため省略
...
),
);
}
コード詳細
// メモリスト編集画面用Widget
class MemoEditPage extends StatefulWidget {
MemoEditPage(this.text);
String text;
@override
_MemoEditPageState createState() => _MemoEditPageState();
}
class _MemoEditPageState extends State<MemoEditPage> {
String _text = "";
@override
void initState() {
super.initState();
// 画面遷移時に渡された値を初期値に設定する
_text = widget.text;
}
// データを元に表示するWidget
@override
Widget build(BuildContext context) {
// 初期値を設定する
final controller = TextEditingController(text: _text);
// TextEditingControllerに設定したテキストのlengthをカーソルの位置に設定する
controller.selection = TextSelection.fromPosition(
TextPosition(offset: controller.text.length),
);
return Scaffold(
appBar: AppBar(
title: Text('MemoEdit'),
),
body: Container(
// 余白を付ける
padding: EdgeInsets.all(64),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// 入力されたテキストを表示
Text(_text, style: TextStyle(color: Colors.brown[300])),
const SizedBox(height: 8),
// テキスト入力箇所
TextField(
controller: controller,
// 入力されたテキストの値を受け取る(valueが入力されたテキスト)
onChanged: (String value) {
// データを変更したことを知らせる(画面を更新する)
setState(() {
// データを変更
_text = value;
});
},
),
const SizedBox(height: 8),
Container(
// 横幅いっぱいに広げる
width: double.infinity,
// Editボタン
child: ElevatedButton(
onPressed: () {
// "pop"で前の画面に戻る
// "pop"の引数から前の画面にデータを渡す
Navigator.of(context).pop(_text);
},
child: Text("Edit", style: TextStyle(color: Colors.white)),
),
),
const SizedBox(height: 8),
Container(
// 横幅いっぱいに広げる
width: double.infinity,
// Cancelボタン
child: TextButton(
// ボタンをクリックした時の処理
onPressed: () {
// "pop"で前の画面に戻る
Navigator.of(context).pop();
},
child: Text("Cancel"),
),
),
],
),
),
);
}
}
さいごに
今回は簡単にアプリの動きを紹介したいため、全てをmain.dart
ファイルに記載してしまったので、フォルダ構成を見直して機能ごとにファイルを分ける必要がありますね。。
また、アプリを終了した際にデータをローカルに保持しておく方法を別の記事で紹介したいと思います!