【Flutter】綺麗で見栄えの良いSafeArea
FlutterのSafeAreaは扱いを間違えると汚くなる問題
FlutterでSafeArea
を考慮した作りをした際、コンテンツをスクロールしている時にbottom部分のSafeArea
で切れて、SafeArea
部分から急にコンテンツがスクロールインしてくる作りをしがちだった。
理由としては、Screen
> SafeArea
> Contents
という階層をイメージして作っているためで、Scaffold
のbodyをSafeArea
で囲むのが構造として正しいと思ってたからだ。
SafeArea
が透明だったならたぶんこれでも問題なかったが、背景色と同じ色で表示されるため、SafeAreaに入った途端Contentsが急に表示されるように見える不自然な実装になる。
iOSのNative実装でも同様なアプリが存在するが、適切な実装をしてやれば、コンテンツをスクロールしている時は、bottom部分のSafeArea
外からスクロールインして、スクロールの終わりはSafeArea
内に収まるようになる。
言葉では伝わりにくいが、SafeArea
の見栄えを意識して実装している開発者からしたら伝わる噴飯物の作りだと思う。
現実 | 理想 |
---|---|
現実と理想の画面下部のを見比べて欲しい。
現実の方は、SafeArea
外は描画すらされてないないが、理想の方は、SafeArea
外でも12が描画されている。
これらをスクロールしていくと、現実の方は、何もない宙から12以降の数字が表示され、見た目も挙動も気持ち悪いものとなる。
日頃から、この理想の形で実装できないものかと頭を悩ませていたが、偶然実現できたのでまとめておこうと思った次第。
汚いSafeAreaになる構造
汚い挙動が発生する構造として、SafeArea
+ SingleChildScrollView
+ Column
の組み合わせが頻出しやすいと思う。
import 'package:flutter/material.dart';
class DirtySafeAreaPage extends StatelessWidget {
const DirtySafeAreaPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
final numbers = [for (int i = 0; i < 20; i++) i];
return Scaffold(
appBar: AppBar(
title: const Text('dirty safe area'),
),
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: numbers
.map(
(e) => ListTile(title: Text('$e')),
)
.toList(),
),
),
),
);
}
}
ListViewを使うと綺麗
ListView
を使えば、適切にSafeArea
のpaddingが考慮されているため、スクロール中はSafeArea外でもコンテンツが表示され、スクロールの終わり時にはSafeArea内にコンテンツが綺麗に収まるる挙動をする。
import 'package:flutter/material.dart';
class ListViewSafeAreaPage extends StatelessWidget {
const ListViewSafeAreaPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
final numbers = [for (int i = 0; i < 20; i++) i];
return Scaffold(
appBar: AppBar(
title: const Text('list view safe area'),
),
body: ListView.builder(
itemCount: numbers.length,
itemBuilder: (context, index) {
final number = numbers[index];
return ListTile(
title: Text('$number'),
);
},
),
);
}
}
綺麗なSafeAreaの構造
SafeArea
をSingleChildScrollView
より下に持ってくることで実現できる
import 'package:flutter/material.dart';
class SmartSafeAreaPage extends StatelessWidget {
const SmartSafeAreaPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
final numbers = [for (int i = 0; i < 20; i++) i];
return Scaffold(
appBar: AppBar(
title: const Text('smart safe area'),
),
body: SingleChildScrollView(
child: SafeArea(
child: Column(
children: numbers
.map(
(e) => ListTile(title: Text('$e')),
)
.toList(),
),
),
),
);
}
}
比較
dirty safe area | list view | smart safe area |
---|---|---|
GitHubで、この記事に用いたコードも公開している。
Discussion