Created
September 8, 2022 21:33
-
-
Save 3t14/eabae009f67be859b2697adf39b35208 to your computer and use it in GitHub Desktop.
端末の画面サイズに応じたMaster Detail型の基本レイアウト構築
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// メモデータに関するモデル処理 | |
// 1件分のメモデータ | |
class Item { | |
final int id; // ユニークな識別子 | |
final String title; // メモのタイトル | |
final String memo; // メモの内容 | |
// コンストラクタ | |
const Item({ | |
this.id = -1, | |
required this.title, | |
required this.memo | |
}); | |
} | |
// 全データ | |
List<Item> items = <Item>[ | |
const Item( | |
id: 0, | |
title: 'Item 1', | |
memo: 'This is the first item.', | |
), | |
const Item( | |
id: 1, | |
title: 'Item 2', | |
memo: 'This is the second item.', | |
), | |
const Item( | |
id: 2, | |
title: 'Item 3', | |
memo: 'This is the third item.', | |
), | |
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// メモデータの詳細表示、編集フォーム | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/material.dart'; | |
import 'item.dart'; | |
const emptyItem = Item( | |
title: '', | |
memo: '', | |
); | |
class ItemDetails extends StatefulWidget { | |
final bool isInTabletLayout; | |
final Item? initItem; // 生成時の初期メモデータ | |
const ItemDetails({ | |
Key? key, | |
required this.isInTabletLayout, | |
// メモデータの初期状態 | |
this.initItem = emptyItem, | |
}) : super(key: key); | |
@override | |
_ItemDetailsState createState() => _ItemDetailsState(); | |
} | |
class _ItemDetailsState extends State<ItemDetails> { | |
var _title = ""; | |
var _memo = ""; | |
var _id = -1; | |
@override | |
void initState() { | |
super.initState(); | |
initData(); | |
} | |
Item _item = emptyItem; | |
// データの初期化 | |
void initData() { | |
var initItem = widget.initItem; | |
initItem ??= emptyItem; // initItemがnullの場合はemptyItemを使用する | |
_title = initItem.title; | |
_memo = initItem.memo; | |
_id = initItem.id; | |
regenerateItem(); | |
} | |
// データを再生成する | |
void regenerateItem() { | |
_item = Item( | |
id: _id, | |
title: _title, | |
memo: _memo, | |
); | |
} | |
@override | |
Widget build(BuildContext context) { | |
if (kDebugMode) print("build ${_id}, ${_title}, ${_memo}"); | |
// 繰り返し使うためスタイルを代入 | |
final textTheme = Theme.of(context).textTheme; | |
var children = [ | |
Text( | |
"タイトル", | |
style: textTheme.caption, | |
), | |
SelectableText( | |
_title, | |
style: textTheme.headlineSmall, | |
), | |
const SizedBox(height: 10), | |
Text( | |
"メモ", | |
style: textTheme.caption, | |
), | |
SelectableText( | |
_memo, | |
style: textTheme.subtitle1, | |
), | |
const SizedBox(height: 48), | |
]; | |
final body = SafeArea( | |
child: Padding(// AppBarを含めるかどうかでPaddingを変える | |
padding: widget.isInTabletLayout | |
? const EdgeInsets.symmetric(horizontal: 72, vertical: 48) | |
: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: children, // 詳細出力の本体 | |
), | |
), | |
); | |
return widget.isInTabletLayout | |
? body | |
: Scaffold( | |
appBar: AppBar( | |
title: const Text("メモ詳細"), | |
), | |
backgroundColor: Colors.yellow, | |
body: body, | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// メモデータのリスト表示 | |
import 'dart:math'; | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/material.dart'; | |
import 'item.dart'; | |
import 'item_details.dart'; | |
class ItemListing extends StatelessWidget { | |
const ItemListing({ | |
Key? key, | |
required this.itemSelectedCallback, | |
this.selectedItem, | |
}): super(key: key); | |
final ValueChanged<Item> itemSelectedCallback; // メモデータが選択された時のコールバック | |
final Item? selectedItem; | |
@override | |
Widget build(BuildContext context) { | |
if (kDebugMode) print('ItemListing.build() ${items.length}, ${selectedItem?.id}, $key'); | |
return ListView.builder( | |
itemCount: items.length, | |
itemBuilder: (BuildContext context, int index) { | |
// データが空の場合は空のウィジェットを返す | |
final item = items.isNotEmpty ? items[index]: emptyItem; | |
return ListTile( // リスト表示するメモデータ1件分のウィジェット | |
leading: const Icon( | |
Icons.edit_note, | |
color: Colors.yellow, | |
size: 24.0, | |
), | |
title: Text(item.title), | |
subtitle: Text(item.memo.substring(0, min(20, item.memo.length))), | |
selected: item.id == selectedItem?.id, | |
onTap: () { // メモデータが選択された時にコールバック経由で親ウィジェットに通知 | |
itemSelectedCallback(item); | |
}, | |
tileColor: Colors.lightBlueAccent, | |
selectedTileColor: Colors.amberAccent, | |
); | |
}, | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// エントリーポイント | |
import 'package:flutter/material.dart'; | |
import 'master_detail_container.dart'; | |
void main() { | |
runApp(const MemoApp()); | |
} | |
class MemoApp extends StatelessWidget { | |
const MemoApp({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: '簡易メモアプリ', | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: const MasterDetailContainer(), | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// モバイル、タブレット双方対応のためのMaster-Detailコンテナー | |
import 'package:flutter/foundation.dart'; | |
import 'item.dart'; | |
import 'item_details.dart'; | |
import 'item_listing.dart'; | |
import 'package:flutter/material.dart'; | |
class MasterDetailContainer extends StatefulWidget { | |
const MasterDetailContainer({Key? key}) : super(key: key); | |
@override | |
_ItemMasterDetailContainerState createState() => | |
_ItemMasterDetailContainerState(); | |
} | |
class _ItemMasterDetailContainerState extends State<MasterDetailContainer> { | |
static const int kTabletBreakpoint = 600; | |
Item? _selectedItem = items.isNotEmpty ? items[0] : null; | |
// モバイル端末か否かを判定する | |
bool get isInMobileLayout => | |
MediaQuery.of(context).size.shortestSide < kTabletBreakpoint; | |
// 詳細画面ウィジェットの生成 | |
Widget get itemDetails => ItemDetails( | |
key: ValueKey(_selectedItem?.id), // 画面更新のために必要 | |
isInTabletLayout: !isInMobileLayout, | |
initItem: _selectedItem, | |
); | |
Widget _buildMobileLayout() { | |
return ItemListing(itemSelectedCallback: (item) async { | |
setState(() { | |
_selectedItem = item; | |
}); | |
await Navigator.of(context).push( | |
MaterialPageRoute( | |
builder: (BuildContext context) => itemDetails, | |
), | |
); | |
}); | |
} | |
Widget _buildTabletLayout() { | |
return Row( | |
children: <Widget>[ | |
Flexible( | |
flex: 1, | |
child: Material( | |
elevation: 4.0, | |
child: ItemListing( | |
itemSelectedCallback: (item) { | |
print("itemSelectedCallback: ${item.title}"); | |
setState(() { | |
_selectedItem = item; | |
}); | |
}, | |
selectedItem: _selectedItem, | |
), | |
), | |
), | |
Flexible( | |
flex: 3, | |
child: itemDetails, | |
), | |
], | |
); | |
} | |
@override | |
Widget build(BuildContext context) { | |
Widget content; | |
if (isInMobileLayout) { | |
content = _buildMobileLayout(); | |
} else { | |
content = _buildTabletLayout(); | |
} | |
return Scaffold( | |
resizeToAvoidBottomInset: false, | |
appBar: AppBar( | |
title: const Text('簡易メモ帳'), | |
), | |
body: SingleChildScrollView( // 1画面に収まらない場合にスクロールする | |
child:SizedBox( | |
height: MediaQuery.of(context).size.height, // 画面の高さ=スクロールの最大値 | |
child:content, // コンテンツを出力 | |
), | |
scrollDirection: Axis.vertical // 縦方向にスクロールする | |
), | |
backgroundColor: Colors.yellow, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment