search
LoginSignup
11
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Flutterで、あるWidgetのレイアウト後の実際のサイズを取得する

FlutterでのUIビルディングは、最終的に表示されるUIの構造(とプロパティ)を、Widgetのツリーで定義します。アプリを作る開発者は、そのUIの各要素が、どのようにレイアウトされレンダリングされたかの詳細を知る必要は、ほとんどありません。

ただし、まれにではありますが、あるWidgetが、実際のレイアウト時にどのようなサイズになっているかを知りたいケースがある……かもしれません。

アプリ開発者が通常扱う Widget とは別の、より低レイヤの Render でレイアウトが行われており、ここまで踏み込むとサイズがわかります。

そこで以下に、あるWidgetのレイアウト・サイズを、コールバックとして受け取るSizeListenableContainerというクラスを実装してみました。

※コードはMITライセンスとします

import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';

typedef SizeChangedCallback = void Function(Size size);

class SizeListenableContainer extends SingleChildRenderObjectWidget {
  const SizeListenableContainer({
    Key key,
    Widget child,
    @required this.onSizeChanged,
  })  : assert(onSizeChanged != null),
        super(key: key, child: child);

  final SizeChangedCallback onSizeChanged;

  @override
  _SizeListenableRenderObject createRenderObject(BuildContext context) {
    return _SizeListenableRenderObject(onSizeChanged: onSizeChanged);
  }
}

class _SizeListenableRenderObject extends RenderProxyBox {
  _SizeListenableRenderObject({
    RenderBox child,
    @required this.onSizeChanged,
  })  : assert(onSizeChanged != null),
        super(child);

  final SizeChangedCallback onSizeChanged;

  Size _oldSize;

  @override
  void performLayout() {
    super.performLayout();

    final Size size = this.size;
    if (size != _oldSize) {
      _oldSize = size;
      _callback(size);
    }
  }

  void _callback(Size size) {
    SchedulerBinding.instance.addPostFrameCallback((Duration _) {
      onSizeChanged(size);
    });
  }
}

これを使う側の例は以下の通り。

class HomePage extends StatefulWidget {
  @override
  HomePageState createState() => HomePageState();
}

class HomePageState extends State<HomePage> {
  String _text = "あ";
  Size _size;

  @override
  Widget build(BuildContext context) {
    final String sizeText = _size?.toString() ?? "null";
    return Scaffold(
      appBar: AppBar(
        title: Text("Widget Size Listening"),
      ),
      body: Container(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          children: <Widget>[
            SizeListenableContainer(
              child: Container(
                color: Colors.blue,
                child: Text(
                  _text,
                  style: const TextStyle(color: Colors.white),
                ),
              ),
              onSizeChanged: (Size size) {
                setState(() {
                  _size = size;
                });
              },
            ),
            Text("上のWidgetのSizeは(${sizeText})です"),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          setState(() {
            _text += "あ";
          });
        },
      ),
    );
  }
}

以下のように、Textを含むContainerのサイズを取得できています。

スクリーンショット 2019-07-30 15.05.08.png

onSizeChangedでsetStateするのはよいですが、それによってSizeListenableContainer.childのサイズが変わるとリビルド・ループになるので、使い方は気をつけてください。


なお、サイズを知りたいというより、レイアウトをコントロールしたいというのが目的あれば CustomSingleChildLayoutCustomMultiChildLayout を使います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
11
Help us understand the problem. What are the problem?