Flutter 上級
このセクションでは、Flutter の上級概念を紹介し、開発スキルの向上に役立てます。
レンダリングの原理
Flutter は Skia(3.0 以降は Impeller に段階的に移行)レンダリングエンジンを使用して UI を直接描画し、ネイティブコンポーネントを使用しません。
Widget ツリーと Element ツリー
- Widget ツリー: UI の設定情報を記述します(不変)
- Element ツリー: Widget のインスタンス化オブジェクト(可変)
- RenderObject ツリー: 実際のレンダリングを担当
レンダリングの原理を理解すると、UI パフォーマンスの最適化に役立ち、不要な再構築を避けられます。
InheritedWidget - データ渡し
InheritedWidget は、Widget ツリーに沿ってデータを下方向に渡すことを可能にし、子 Widget は祖先のデータを取得できます。
例:カスタム InheritedWidget
// データコンテナを定義
class MyData extends InheritedWidget {
final int value;
const MyData({
super.key,
required this.value,
required super.child,
});
// 便利メソッド:最も近い MyData を取得
static MyData of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyData>()!;
}
// 子 Widget を再構築する必要があるかどうかを判断
@override
bool updateShouldNotify(MyData oldWidget) {
return value != oldWidget.value;
}
}
// 使用
class ParentWidget extends StatelessWidget {
const ParentWidget({super.key});
@override
Widget build(BuildContext context) {
return MyData(
value: 42,
child: const ChildWidget(),
);
}
}
class ChildWidget extends StatelessWidget {
const ChildWidget({super.key});
@override
Widget build(BuildContext context) {
// 祖先の MyData を取得
final myData = MyData.of(context);
return Text('Value: ${myData.value}');
}
}
class MyData extends InheritedWidget {
final int value;
const MyData({
super.key,
required this.value,
required super.child,
});
// 便利メソッド:最も近い MyData を取得
static MyData of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyData>()!;
}
// 子 Widget を再構築する必要があるかどうかを判断
@override
bool updateShouldNotify(MyData oldWidget) {
return value != oldWidget.value;
}
}
// 使用
class ParentWidget extends StatelessWidget {
const ParentWidget({super.key});
@override
Widget build(BuildContext context) {
return MyData(
value: 42,
child: const ChildWidget(),
);
}
}
class ChildWidget extends StatelessWidget {
const ChildWidget({super.key});
@override
Widget build(BuildContext context) {
// 祖先の MyData を取得
final myData = MyData.of(context);
return Text('Value: ${myData.value}');
}
}
RepaintBoundary - 部分再描画
RepaintBoundary を使用すると、再描画領域を制限し、パフォーマンスを向上できます。
例:RepaintBoundary の使用
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: Container(
// この領域の描画は外部に影響しません
color: Colors.red,
child: const Text('独立領域'),
),
);
}
}
const MyWidget({super.key});
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: Container(
// この領域の描画は外部に影響しません
color: Colors.red,
child: const Text('独立領域'),
),
);
}
}
Keys の深い理解
Keys は Flutter が Widget を識別し、新しい Widget と既存の Widget を区別するのに役立ちます。
ValueKey vs ObjectKey vs UniqueKey
| タイプ | 説明 |
|---|---|
| ValueKey | 値をキーとして使用(ID、数値、文字列など) |
| ObjectKey | オブジェクト参照をキーとして使用 |
| UniqueKey | 作成ごとに一意のキーを生成し、再構築を妨げる |
CustomPainter - カスタム描画
CustomPainter を使用すると、完全にカスタマイズされた図形描画を実現できます。
例:CustomPainter の使用
class CustomPainterExample extends StatelessWidget {
const CustomPainterExample({super.key});
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: MyCirclePainter(),
size: const Size(200, 200),
);
}
}
class MyCirclePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 円を描画
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2 - 10;
canvas.drawCircle(center, radius, paint);
// 枠線を描画
final borderPaint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawCircle(center, radius, borderPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
const CustomPainterExample({super.key});
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: MyCirclePainter(),
size: const Size(200, 200),
);
}
}
class MyCirclePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 円を描画
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2 - 10;
canvas.drawCircle(center, radius, paint);
// 枠線を描画
final borderPaint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawCircle(center, radius, borderPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
Stream と Future
例:Stream の使い方
// Stream を作成
Stream<int> countStream(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(const Duration(seconds: 1));
yield i; // 値を送信
}
}
// StreamBuilder を使用
class StreamExample extends StatelessWidget {
const StreamExample({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: countStream(10),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('カウント: ${snapshot.data}');
} else if (snapshot.hasError) {
return Text('エラー: ${snapshot.error}');
}
return const CircularProgressIndicator();
},
);
}
}
Stream<int> countStream(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(const Duration(seconds: 1));
yield i; // 値を送信
}
}
// StreamBuilder を使用
class StreamExample extends StatelessWidget {
const StreamExample({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: countStream(10),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('カウント: ${snapshot.data}');
} else if (snapshot.hasError) {
return Text('エラー: ${snapshot.error}');
}
return const CircularProgressIndicator();
},
);
}
}
まとめ
この Flutter 入門チュートリアルでは、以下の内容をカバーしています:
- Flutter のインストールと環境設定
- Widget の基本(StatelessWidget と StatefulWidget)
- レイアウトシステム(Row、Column、Stack)
- ユーザー入力の処理
- 状態管理(setState、Provider)
- ネットワークリクエストとデータストレージ
- ナビゲーションとルーティング
- テストと公開
さらに深く学ぶには、以下に注目してください:
- BLoC パターン
- Riverpod 状態管理
- GetX ルーティングと状態管理
- Flutter パフォーマンス最適化
- Flutter Web とデスクトップ開発