Skip to content

Instantly share code, notes, and snippets.

@3t14
Created September 8, 2022 21:33
Show Gist options
  • Save 3t14/eabae009f67be859b2697adf39b35208 to your computer and use it in GitHub Desktop.
Save 3t14/eabae009f67be859b2697adf39b35208 to your computer and use it in GitHub Desktop.
端末の画面サイズに応じたMaster Detail型の基本レイアウト構築
// メモデータに関するモデル処理
// 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.',
),
];
// メモデータの詳細表示、編集フォーム
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,
);
}
}
// メモデータのリスト表示
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,
);
},
);
}
}
// エントリーポイント
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(),
);
}
}
// モバイル、タブレット双方対応のための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