Help us understand the problem. What is going on with this article?

Flutter RenderObjectWidgetを利用したグラフィックス描画

はじめに

Flutter のスーパークラスには以下の4種類があります。その他のウィジェット (クラス) はこれらを継承した派生クラスです。

クラス名 用途
StatelessWidget 状態 (Stateクラス) を持たない静的なウィジェットのコンテナ (固定ページ用)
StatefulWidget 状態 (Stateクラス) を持ち、動的変化があるウィジェットのコンテナ (動的変化があるページ用)
InheritedWidget ウィジェットツリーの上位の情報を子ノードについた得るためのウィジェット
RenderObjectWidget グラフィックスをレンダリングするオブジェクトのRenderObjectのコンテナとなるウィジェット

今回は一番下のRenderObjectWidgetを利用したグラフィックス描画について解説します。あまり見慣れないものですが、Flutterのレンダリング過程で重要になってくるクラスなのと、Flutterでゲームアプリを作る場合には利用すると思います。

RenderObjectWidgetとは

RenderObjectWidgetとは、グラフィックスの描画 (レンダリング) オブジェクトであるRenderObjectのコンテナになるウィジェットのことです。このウィジェットの中に描画対象のウィジェットを格納して利用します。

これらはFlutterのレンダリングフローの中でも重要な働きをしているため、別途どこかか (別の記事) で解説したいと思います。

グラフィックス描画してみる

RenderObjectWidgetの使い方を知るために、簡単なペイント程度のサンプルを解説します。
Androidで言うところのCanvasと同じようなイメージです。
flutter_01.png

メインページの作成

適当にメインページを用意します。_RenderBoxWidgetがグラフィックスのサンプル用の独自クラスです。

main_page.dart
class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter RenderObjectWidget Example'),
      ),
      body: Center(
        child: _RenderBoxWidget(),
      ),
    );
  }
}

描画用コンテナクラスの作成

_RenderBoxWidgetの実装は以下です。

_RenderBoxWidgetクラス
class _RenderBoxWidget extends SingleChildRenderObjectWidget {
  @override
  RenderObject createRenderObject(BuildContext context) {
    return _MyRenderBox();
  }
}

SingleChildRenderObjectWidgetクラスを継承し、createRenderObjectメソッドの中でRenderObjectを生成します。

SingleChildRenderObjectWidgetRenderObjectWidgetの派生クラスで、一つだけの子ウィジェット (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 入門でつまづいたところまとめを参考に実装しました。

kurun_pan
記事の内容以外についての、ご意見・連絡等はTwitterの方へお願いします!
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした