はじめに
前回はFlutter Widget of the Weekの「#13FadeInImage」、「#14 StreamBuilder」を紹介しました。
今回はその続きで「#15 InheritedModel」、「#16 ClipRRect」、「#17 Hero」の3つです。
の記事はこちら
Flutter Widget of the Week
環境
- Flutter 2.8.1
記事にした時点でのバージョンです。GitHubに公開しているのは常に最新の安定版(stable)を使う予定です。
#15 InheritedModel
InheritedModelとは
InheritedWidgetと組み合わせて利用します。
InheritedWidgetの変更を購読している子孫のウィジェットの中で、一部のウィジェットだけに変更を伝えたい場合に利用します。
そうすることによりウィジェット全体をビルドすることなく効率的に画面をビルドすることができます。
サンプルコード
import 'package:flutter/material.dart';
class SamplePage015 extends StatelessWidget {
const SamplePage015({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('InheritedModel'),
),
body: _SamplePage015Stateful(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
_SamplePage015ColorOneWidget(),
_SamplePage015ColorTwoWidget(),
_SamplePage015ColorChangerWidget(),
],
),
),
);
}
}
class _SamplePage015Stateful extends StatefulWidget {
const _SamplePage015Stateful({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
@override
_SamplePage015StatefulState createState() => _SamplePage015StatefulState();
}
class _SamplePage015StatefulState extends State<_SamplePage015Stateful> {
Color colorOne = Colors.red;
Color colorTwo = Colors.blue;
static _SamplePage015StatefulState? of(BuildContext context) {
return context.findAncestorStateOfType<_SamplePage015StatefulState>();
}
void changeColorOne() {
setState(() {
colorOne = (colorOne == Colors.red) ? Colors.green : Colors.red;
});
}
void changeColorTwo() {
setState(() {
colorTwo = (colorTwo == Colors.blue) ? Colors.yellow : Colors.blue;
});
}
@override
Widget build(BuildContext context) {
return _SamplePage015InheritedModel(
colorOne,
colorTwo,
widget.child,
);
}
}
class _SamplePage015InheritedModel extends InheritedModel<String> {
const _SamplePage015InheritedModel(
this.colorOne,
this.colorTwo,
Widget child,
) : super(child: child);
final Color colorOne;
final Color colorTwo;
// ignore: unused_element
static _SamplePage015InheritedModel? of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<_SamplePage015InheritedModel>();
}
@override
bool updateShouldNotify(_SamplePage015InheritedModel oldWidget) {
return colorOne != oldWidget.colorOne || colorTwo != oldWidget.colorTwo;
}
@override
bool updateShouldNotifyDependent(
_SamplePage015InheritedModel oldWidget,
Set<String> dependencies,
) {
if (dependencies.contains('one') && colorOne != oldWidget.colorOne) {
return true;
}
if (dependencies.contains('two') && colorTwo != oldWidget.colorTwo) {
return true;
}
return false;
}
}
class _SamplePage015ColorOneWidget extends StatelessWidget {
const _SamplePage015ColorOneWidget({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
// ignore: avoid_print
print('_SamplePage015ColorOneWidget#build');
final inherited = InheritedModel.inheritFrom<_SamplePage015InheritedModel>(
context,
aspect: 'one',
);
return Container(
color: inherited?.colorOne,
height: 100,
width: 100,
);
}
}
class _SamplePage015ColorTwoWidget extends StatelessWidget {
const _SamplePage015ColorTwoWidget({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
// ignore: avoid_print
print('_SamplePage015ColorTwoWidget#build');
final inherited = InheritedModel.inheritFrom<_SamplePage015InheritedModel>(
context,
aspect: 'two',
);
return Container(
color: inherited?.colorTwo,
height: 100,
width: 100,
);
}
}
class _SamplePage015ColorChangerWidget extends StatelessWidget {
const _SamplePage015ColorChangerWidget({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
// ignore: avoid_print
print('_SamplePage015ColorChangerWidget#build');
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () =>
_SamplePage015StatefulState.of(context)?.changeColorOne(),
child: const Text('Change colorOne'),
),
ElevatedButton(
onPressed: () =>
_SamplePage015StatefulState.of(context)?.changeColorTwo(),
child: const Text('Change colorTwo'),
),
],
);
}
}
結果
初期画面
「Change colorOne」を押下すると上の色が変わります。
その時にログを見ると
flutter: _SamplePage015ColorOneWidget#build
不要なビルドが発生していないのがわかります。
次に「Change colorTwo」を押下すると下の色が変わります。
その時にログを見ると
flutter: _SamplePage015ColorTwoWidget#build
こちらも不要なビルドが発生していないのがわかります。
動画
公式リファレンス
#16 ClipRRect
ClipRRectとは
画像やコンテンツの角をきれいに丸くしてくれるウィジェットです。
ClipOvalやClipPathなど類似のウィジェットもあるようです。
サンプルコード
import 'package:flutter/material.dart';
class SamplePage016 extends StatelessWidget {
const SamplePage016({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ClipRRect'),
),
body: SafeArea(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('ClipRRect'),
ClipRRect(
borderRadius: BorderRadius.circular(15),
child: _SamplePage016ChildWidget(),
),
const SizedBox(height: 20),
const Text('ClipOval'),
ClipOval(
child: _SamplePage016ChildWidget(),
),
const SizedBox(height: 20),
const Text('ClipPath'),
ClipPath(
clipper: _SamplePage016CustomClipperPath(),
child: _SamplePage016ChildWidget(),
)
],
),
),
),
);
}
}
class _SamplePage016ChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 128,
color: Colors.blue,
child: const Center(
child: FlutterLogo(
size: 100,
),
),
);
}
}
class _SamplePage016CustomClipperPath extends CustomClipper<Path> {
@override
Path getClip(Size size) {
final path = Path()
..moveTo(size.width / 2, 0)
..lineTo(size.width, size.height)
..lineTo(0, size.height)
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
結果
動画
公式リファレンス
#17 Hero
Heroとは
違う画面で同一の画像を利用している際に利用するときにオシャレに画面遷移することができます。
言葉で説明するよりも見たほうが早いと思います。
サンプルコード
import 'package:flutter/material.dart';
class SamplePage017 extends StatelessWidget {
const SamplePage017({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Hero'),
),
body: SafeArea(
child: ListView(
children: [
ListTile(
leading: Hero(
tag: 'sample017',
child: ClipOval(
child: Image.asset('assets/sample.png'),
),
),
title: const Text('Hero Sample'),
trailing: const Icon(Icons.navigate_next),
onTap: () {
Navigator.push<void>(
context,
MaterialPageRoute(
builder: (context) {
return _SamplePage017DetailPage();
},
),
);
},
),
],
),
),
);
}
}
class _SamplePage017DetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Hero'),
),
body: SafeArea(
child: Column(
children: [
Center(
child: Hero(
tag: 'sample017',
child: Image.asset('assets/sample.png'),
),
),
const SizedBox(height: 20),
const Text('Hero Sample')
],
),
),
);
}
}
結果
ListTileを選択すると
画面遷移しながらシームレスに画像が拡大したようなアニメーションをしてくれます。
動画
公式リファレンス
さいごに
InheritedModelはイマイチうまくできなかったので、使うことはなさそうです。
ClipRRect系の自分でPathとかを考えるのは時間がかかりそうですね。
Heroは今後使ってみたいと思いました。
コメント