はじめに
Flutter のスーパークラスには以下の4種類があります。その他のウィジェット (クラス) はこれらを継承した派生クラスです。
クラス名 | 用途 |
---|---|
StatelessWidget |
状態 (Stateクラス) を持たない静的なウィジェットのコンテナ (固定ページ用) |
StatefulWidget |
状態 (Stateクラス) を持ち、動的変化があるウィジェットのコンテナ (動的変化があるページ用) |
InheritedWidget |
ウィジェットツリーの上位の情報を子ノードについた得るためのウィジェット |
RenderObjectWidget |
グラフィックスをレンダリングするオブジェクトのRenderObjectのコンテナとなるウィジェット |
今回は一番下のRenderObjectWidget
を利用したグラフィックス描画について解説します。あまり見慣れないものですが、Flutterのレンダリング過程で重要になってくるクラスなのと、Flutterでゲームアプリを作る場合には利用すると思います。
RenderObjectWidgetとは
RenderObjectWidget
とは、グラフィックスの描画 (レンダリング) オブジェクトであるRenderObject
のコンテナになるウィジェットのことです。このウィジェットの中に描画対象のウィジェットを格納して利用します。
これらはFlutterのレンダリングフローの中でも重要な働きをしているため、別途どこかか (別の記事) で解説したいと思います。
グラフィックス描画してみる
RenderObjectWidget
の使い方を知るために、簡単なペイント程度のサンプルを解説します。
Androidで言うところのCanvas
と同じようなイメージです。
メインページの作成
適当にメインページを用意します。_RenderBoxWidget
がグラフィックスのサンプル用の独自クラスです。
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter RenderObjectWidget Example'),
),
body: Center(
child: _RenderBoxWidget(),
),
);
}
}
描画用コンテナクラスの作成
_RenderBoxWidget
の実装は以下です。
class _RenderBoxWidget extends SingleChildRenderObjectWidget {
@override
RenderObject createRenderObject(BuildContext context) {
return _MyRenderBox();
}
}
SingleChildRenderObjectWidget
クラスを継承し、createRenderObject
メソッドの中でRenderObject
を生成します。
SingleChildRenderObjectWidget
はRenderObjectWidget
の派生クラスで、一つだけの子ウィジェット (child) を持つことができます。
SingleChildRenderObjectWidget
の他には、複数の子ウィジェット (children) を持てるMultiChildRenderObjectWidget
や、アプリ開発者が直接は扱う事がない基底クラスのLeafRenderObjectWidget
が存在します。
描画クラスウィジェットの作成
描画処理はRenderBox
ウィジェットが担当しています。
なのでRenderBox
継承したクラスを作成してpaint()
の中で実際の描画処理を行います。
ここまで来れば実際の描画処理はほとんどAndroidのCanvas
と同じ操作で簡単です。
paint()
の引数のPaintingContext
とは、描画処理するため制御情報を保有しているコンテキストのことで、このコンテキストのキャンパスに描画したいデータを書き込みます。それと注意点として、paint()
の引数のOffsetには画面内の描画開始 (ウィジェットが配置された位置) の情報が来るため、そこからのオフセットという座標軸で描画を行うことになります。
あとは、Paintで描画対象の色や塗り潰しなどを定義し、四角形を描画する場合はRect
を利用してサイズを指定します。
最後にCanvas
で描画すればOKという感じです。この辺は解説しなくてもソースコードを見たら雰囲気でわかるかと思います。
class _MyRenderBox extends RenderBox {
/*
以下のエラー対応
flutter: The following assertion was thrown during performLayout():
flutter: _MyRenderBox did not implement performLayout().
flutter: RenderBox subclasses need to either override performLayout() to set a size and lay out any children,
flutter: or, set sizedByParent to true so that performResize() sizes the render object.
*/
@override
bool get sizedByParent => true;
@override
void performResize() {
size = constraints.biggest;
}
@override
void paint(PaintingContext context, Offset offset) {
Canvas c = context.canvas;
// offsetのdx, dyにはスクリーン内の描画開始エリア情報 (オフセット) が入っています
double dx = offset.dx;
double dy = offset.dy;
Paint p = Paint();
p.style = PaintingStyle.fill; // 図形内部を塗りつぶす
p.color = Color.fromARGB(255, 255, 0, 0);
Rect r = Rect.fromLTWH(dx + 10.0, dy + 10.0, 100.0, 100.0);
c.drawRect(r, p);
p.style = PaintingStyle.stroke; // 図形内部を塗り潰さない
p.color = Color.fromARGB(255, 0, 255, 0);
p.strokeWidth = 10.0; // 線の太さ
r = Rect.fromLTWH(dx + 150.0, dy + 110.0, 100.0, 100.0);
c.drawRect(r, p);
// 丸を描画する場合も四角とほぼ同じ感じです
p.style = PaintingStyle.fill;
p.color = Color.fromARGB(255, 0, 0, 255);
Offset ctr = Offset(dx + 350.0, dy + 260.0);
c.drawCircle(ctr, 50.0, p);
}
}
アサートエラー対応
sizedByParent()
とperformResize()
については、無くても動作自体はするのですが、実行時にアサートでエラーログが出るため、Flutter 入門でつまづいたところまとめを参考に実装しました。