Flutter CupertinoAppでドロワーを表示する
はじめに
MaterialAppでは、Scaffoldのdrawerを使って簡単にドロワーを表示することができます。
しかし、CupertinoAppのCupertinoPageScaffold等にはdrawerは用意されていません(Issue #51315でもFlutterでは提供しない旨コメントされています)。
この記事ではCupertinoAppでドロワーを表示する方法について説明します。
前提
- この記事のサンプルはFlutter 3.3.8で動かしました。
- MaterialAppではなくCupertinoAppでドロワーを表示する方法です。MaterialAppで表示したい場合、Scaffoldのdrawerを使うことで簡単に表示できます。Add a drawer to a screenをご参照ください。
- こちらのStackOverflowではStackとAnimatedPositionedを使った方法が紹介されています。この記事ではこの方法ではなくNavigator.push()して表示する方法を説明します。
- この記事で説明する方法は、ボタンをタップした時にドロワーを開く方法です。画面をスワイプ・ドラッグして開く方法ではありません。画面をスワイプ・ドラッグして開く方法鵜を知りたい場合、当ブログのFlutterで画面をドラッグ・スワイプで開くをご参照ください。
ポイント
- ドロワー画面のWidgetをNavigator.push()する際に、CupertinoPageRouteではなくPageRouteBuilderを渡します。
- PageRouteBuilderのtransitionBuilderで、AnimatedWidgetを返すようにすることで、任意のアニメーションでドロワー画面を開けるようになります。
- 今回、AnimatedWidgetとして、SlideTransitionを返しています。これによって、左から右にスライドする動きを実現しています。
コード全体
import 'package:flutter/cupertino.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const CupertinoApp(
title: 'Flutter Demo',
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
leading: CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () => _showDrawer(context),
alignment: Alignment.center,
child: const Text('Open'),
),
middle: Text(widget.title),
),
child: const SizedBox(),
);
}
Future<void> _showDrawer(BuildContext context) {
return Navigator.push(
context,
PageRouteBuilder(
transitionDuration: const Duration(milliseconds: 200),
reverseTransitionDuration: const Duration(milliseconds: 200),
pageBuilder: (context, animation, secondaryAnimation) => const _DrawerPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(-1.0, 0.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
),
);
}
}
class _DrawerPage extends StatelessWidget {
const _DrawerPage({super.key});
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
backgroundColor: const Color(0x00000000),
child: Stack(
children: [
Container(color: const Color(0xFF0000FF)),
Positioned(
top: 16,
right: 16,
child: SafeArea(
child: CupertinoButton(
onPressed: () => Navigator.pop(context),
child: const Text(
'Close',
style: TextStyle(color: Color(0xFFFFFFFF)),
),
),
),
)
],
),
);
}
}
解説
Navigator.push()にPageRouteBuilderを渡す
-
通常Navigator.push()する際はCupertinoPageRouteを渡すことが多いと思いますが、以下のコードの(1)の通りPageRouteBuilderを渡します。
- PageRouteBuilderは、CupertinoPageRouteやMaterialPageRouteと同様にPageRouteのサブクラスです。
- PageRouteBuilderは、PageRouteのサブクラスを作らなくてもそのcallbackを実装することによってPageRouteを定義できるようにしたユーティリティクラスです。
The PageRouteBuilder subclass provides a way to create a PageRoute using callbacks rather than by defining a new class via subclassing.
PageRoute
-
PageRouteBuilderのpageBuilderで開きたいドロワー画面のWidget(_DrawerPage)を返すようにしています。
// ...省略...
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
leading: CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () => _showDrawer(context),
alignment: Alignment.center,
child: const Text('Open'),
),
middle: Text(widget.title),
),
child: const SizedBox(),
);
}
Future<void> _showDrawer(BuildContext context) {
return Navigator.push(
context,
// (1) CupertinoPageRouteではなく、PageRouteBuilderを渡す。
PageRouteBuilder(
transitionDuration: const Duration(milliseconds: 200),
reverseTransitionDuration: const Duration(milliseconds: 200),
pageBuilder: (context, animation, secondaryAnimation) => const _DrawerPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(-1.0, 0.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
// (2) CupertinoPageRouteではなく、PageRouteBuilderを渡す。
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
),
);
}
}
// ...省略...
PageRouteBuilderのtransitionBuilderでSlideTransitionを返す。
- 上記コードの(2)の部分では、今回スライドでドロワーを表示したいのでSlideTransitionを使っています。SlideTransitionはAnimatedWidgetのサブクラスで、
position:
にAnimationを渡す必要があります。 - transitionBuilderで渡ってきたanimationをSlideTransitionに渡しています。そのままanimationを渡すこともできますが、緩急をつけるためCurveTweenをかませています。これにより、一定速度のアニメーションが緩急がついたアニメーションに変換されます。
- このコードは、Flutter公式のAnimate a page route transitionを参考にしています。この公式ドキュメントは下から上にスライドして表示されます(この記事では左から右にスライドして表示されます)。
最後に
最後までお読みくださりありがとうございます。
今回ドロワーをスライドアニメーション付きでpushする方法を説明しました。
今回の方法は、フェードイン・フェードアウトアニメーション付きでpushするときなどにも応用できると思います。
ぜひ使ってみてください。
コメント