ウィジェットの高さを取得するにはどうすればよいですか?


90

LayoutBuilderウィジェットの高さを取得するためにどのように使用されるのかわかりません。

ウィジェットのリストを表示して高さを取得し、特別なスクロール効果を計算できるようにする必要があります。私はパッケージを開発していて、他の開発者がウィジェットを提供しています(私はそれらを制御していません)。LayoutBuilderを使用して高さを取得できることを読みました。

非常に単純なケースでは、WidgetをLayoutBuilder.builderでラップしてスタックに入れようminHeight 0.0としmaxHeight INFINITYましたが、常に、、およびを取得します。LayoutBuilderを誤用していますか?

編集:LayoutBuilderはダメのようです。私はほとんど解決策であるCustomSingleChildLayoutを見つけました。

そのデリゲートを拡張し、getPositionForChild(Size size, Size childSize)メソッドでウィジェットの高さを取得することができました。しかし、最初に呼び出されるメソッドはSize getSize(BoxConstraints constraints)、制約として、これらのCustomSingleChildLayoutsをListViewに配置しているため、0からINFINITYになります。

私の問題は、SingleChildLayoutDelegategetSizeがビューの高さを返す必要があるように動作することです。その時の子供の身長はわかりません。私はconstraints.smallest(0、高さは0)、または無限大でアプリをクラッシュさせるconstraints.biggestのみを返すことができます。

ドキュメントでは、次のようにも書かれています。

...しかし、親のサイズは子のサイズに依存することはできません。

そして、それは奇妙な制限です。


1
LayoutBuilderは、親のボックス制約を提供します。子供のサイズが必要な場合は、別の戦略が必要です。私が指摘できる1つの例は、Wrapウィジェットです。これは、関連付けられたRenderWrapクラスの子のサイズに基づいてレイアウトを行います。ただし、これはレイアウト中に発生し、build()では発生しません。
ジョナウィリアムズ

@JonahWilliamsうーん。Wrapは、子をレイアウトするように設計されたウィジェットであるため、どのように役立つかわかりません(Webのflexboxグリッドのように機能します)。高さを見つける必要がある子ウィジェットが1つあります。質問の編集をご覧ください。CustomSingleChildLayoutの問題はほぼ解決しましたが、その制限に固執しました。
グディン2018年

あなたが欲しいものをより具体的に説明できますか?複数の解決策があります。ただし、それぞれに異なるユースケースがあります。
レミRousselet

承知しました。パッケージを開発しています。ユーザー/開発者は私のクラスにウィジェットを提供します。ここではnew Text("hello")、より複雑なウィジェットまで、あらゆるウィジェットについて話します。これらのウィジェットをListViewに配置し、スクロール効果を計算するためにそれらの高さが必要です。SingleChildLayoutDelegateが行っているのと同じように、レイアウト時に高さを取得しても問題ありません。
グディン2018年

「スクロール効果」とはどういう意味ですか?例はありますか?
レミRousselet

回答:


159

画面上のウィジェットのサイズ/位置を取得するには、ウィジェットGlobalKeyを取得しBuildContextRenderBox、その特定のウィジェットのを検索します。これには、グローバルな位置とレンダリングされたサイズが含まれます。

注意すべき点が1つだけあります。ウィジェットがレンダリングされていない場合、そのコンテキストは存在しない可能性があります。ListViewウィジェットは潜在的に表示されている場合にのみレンダリングされるため、問題が発生する可能性があります。

もう1つの問題は、ウィジェットがまだレンダリングされていないため、呼び出しRenderBox中にウィジェットを取得できないことですbuild


しかし、ビルド中にサイズを調整する必要があります!私に何ができる?

役立つことができる1つのクールなウィジェットがあります:OverlayそしてそのOverlayEntry。それらは、他のすべての上にウィジェットを表示するために使用されます(スタックと同様)。

しかし、最もクールなことは、それらが異なるbuildフロー上にあることです。それらは通常のウィジェットの後に構築さます。

これには、非常に優れた意味が1つありOverlayEntryます。サイズは、実際のウィジェットツリーのウィジェットに依存する可能性があります。


はい。しかし、OverlayEntryを手動で再構築する必要はありませんか?

はい、彼らはやる。ただしScrollController、注意すべきもう1つのことがあります。に渡されるScrollable、は、に似たリスナブルAnimationControllerです。

あなたはどちら組み合わせることができ意味AnimatedBuilderを持つがScrollController、それはスクロールに自動的にウィジェットを再構築するために素敵な効果を持っているでしょう。この状況に最適ですよね?


すべてを1つの例にまとめます。

次の例では、内部のウィジェットに続きListView、同じ高さを共有するオーバーレイが表示されます。

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final controller = ScrollController();
  OverlayEntry sticky;
  GlobalKey stickyKey = GlobalKey();

  @override
  void initState() {
    if (sticky != null) {
      sticky.remove();
    }
    sticky = OverlayEntry(
      builder: (context) => stickyBuilder(context),
    );

    SchedulerBinding.instance.addPostFrameCallback((_) {
      Overlay.of(context).insert(sticky);
    });

    super.initState();
  }

  @override
  void dispose() {
    sticky.remove();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        controller: controller,
        itemBuilder: (context, index) {
          if (index == 6) {
            return Container(
              key: stickyKey,
              height: 100.0,
              color: Colors.green,
              child: const Text("I'm fat"),
            );
          }
          return ListTile(
            title: Text(
              'Hello $index',
              style: const TextStyle(color: Colors.white),
            ),
          );
        },
      ),
    );
  }

  Widget stickyBuilder(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (_,Widget child) {
        final keyContext = stickyKey.currentContext;
        if (keyContext != null) {
          // widget is visible
          final box = keyContext.findRenderObject() as RenderBox;
          final pos = box.localToGlobal(Offset.zero);
          return Positioned(
            top: pos.dy + box.size.height,
            left: 50.0,
            right: 50.0,
            height: box.size.height,
            child: Material(
              child: Container(
                alignment: Alignment.center,
                color: Colors.purple,
                child: const Text("^ Nah I think you're okay"),
              ),
            ),
          );
        }
        return Container();
      },
    );
  }
}

別の画面に移動するときは、次の呼び出しを呼び出します。そうしないと、スティッキーが表示されたままになります。

sticky.remove();

5
さて、ついにそれをテストする時間ができました。だから、私は実際には最初の部分だけが必要でした。でcontext.heightにアクセスできることを知りませんでしたGlobalKey。素晴らしい答え。
グディン2018

1
スケジュールバインダーをインポートする方法は?
siddhesh 2018

1
'package:flutter /scheduler.dart。'をインポートしてみました。しかし、私はエラーターゲットを得たURIが存在しません@レミ・rousselet
siddhesh

@rémi-rousseletListViewの背後にウィジェットがあり、その高さをListViewの高さに応じて制御したい場合、これを機能させるにはどうすればよいですか?
RoyalGriffin

2
レミ、賢い解決策と素晴らしい説明をありがとう。質問があります。が押されRectListView.builderときにのListItemのを知りたい場合はどうなりますか。GlobalKey listItemKey = GlobalKey();すべてのlistItemに設定するのはやり過ぎでしょうか?+10000個のアイテムがあるとしましょう。パフォーマンス/メモリの問題なしにこれを管理する賢い方法はありますか?
aytunch

50

これは(私が思うに)これを行う最も簡単な方法です。

以下をコピーしてプロジェクトに貼り付けます。

更新:RenderProxyBox子とその子孫の再構築のたびに呼び出されるため、使用すると実装が少し正確になります。これは、最上位のbuild()メソッドでは常に当てはまるとは限りません。

注:ここでHixieが指摘しているように、これはこれを行うための正確に効率的な方法ではありません。しかし、それが最も簡単です。

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

typedef void OnWidgetSizeChange(Size size);

class MeasureSizeRenderObject extends RenderProxyBox {
  Size oldSize;
  final OnWidgetSizeChange onChange;

  MeasureSizeRenderObject(this.onChange);

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

    Size newSize = child.size;
    if (oldSize == newSize) return;

    oldSize = newSize;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      onChange(newSize);
    });
  }
}

class MeasureSize extends SingleChildRenderObjectWidget {
  final OnWidgetSizeChange onChange;

  const MeasureSize({
    Key key,
    @required this.onChange,
    @required Widget child,
  }) : super(key: key, child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return MeasureSizeRenderObject(onChange);
  }
}

次に、サイズを測定するウィジェットを。でラップするだけですMeasureSize


var myChildSize = Size.zero;

Widget build(BuildContext context) {
  return ...( 
    child: MeasureSize(
      onChange: (size) {
        setState(() {
          myChildSize = size;
        });
      },
      child: ...
    ),
  );
}

そうです、あなたが十分に努力すれば、親のサイズは子供のサイズに依存することは できません


個人的な逸話-これはAlign、のようなウィジェットのサイズを制限するのに便利です。これは、途方もない量のスペースを占めるのが好きです。


ここで得たものは素晴らしいです。非常識な説明はありません..動作するコードだけです。ありがとう!
DannyDaglas20年

1
素晴らしいソリューションの兄弟。これをパブパッケージとして作成します。
厳しいビカディア

多くの高さと幅の問題に対するその非常に優れたソリューション、ありがとう
alirezadaryani20年

これは機能しますが、場所によっては使いにくい場合があります。たとえば、PreferredSizeWidgetでは、preferredSizeは1回しか呼び出されないため、簡単な方法で高さを設定することはできません。
user22337 0620年

ねえ、build()が再構築のために呼び出されない場合をサポートするように実装を更新しました。これがもっと正しいことを願っています。
DevAggarwal20年

6

私が正しく理解していれば、任意のウィジェットの寸法を測定したいので、それらのウィジェットを別のウィジェットでラップすることができます。その場合、この回答の方法がうまくいくはずです。

基本的に、解決策は、ウィジェットのライフサイクルでコールバックをバインドすることですcontext.size。これは、最初のフレームがレンダリングされた後に呼び出され、そこからアクセスできます。キャッチは、測定するウィジェットをステートフルウィジェット内にラップする必要があることです。また、サイズがどうしても必要な場合はbuild()、2番目のレンダリングでのみアクセスできます(最初のレンダリング後にのみ使用できます)。


1
ポインタをありがとう、私の完全なソリューションをチェックしてください-stackoverflow.com/a/60868972/7061265
DevAggarwal20年

3

LayoutBuilderウィジェットのサイズを決定するために使用できる方法のサンプルを次に示します。

以来LayoutBuilderウィジェットは親ウィジェットの制約を決定することができる、その使用例1は、その子ウィジェットは親の寸法に適応持つことができるようにすることです。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var dimension = 40.0;

  increaseWidgetSize() {
    setState(() {
      dimension += 20;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(children: <Widget>[
          Text('Dimension: $dimension'),
          Container(
            color: Colors.teal,
            alignment: Alignment.center,
            height: dimension,
            width: dimension,
            // LayoutBuilder inherits its parent widget's dimension. In this case, the Container in teal
            child: LayoutBuilder(builder: (context, constraints) {
              debugPrint('Max height: ${constraints.maxHeight}, max width: ${constraints.maxWidth}');
              return Container(); // create function here to adapt to the parent widget's constraints
            }),
          ),
        ]),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: increaseWidgetSize,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

デモ

デモ

ログ

I/flutter (26712): Max height: 40.0, max width: 40.0
I/flutter (26712): Max height: 60.0, max width: 60.0
I/flutter (26712): Max height: 80.0, max width: 80.0
I/flutter (26712): Max height: 100.0, max width: 100.0

動的サイズでは機能しません
RenanCoelho20年

問題の原因となる「動的サイズ」について詳しく教えてください。私が確認できる最小限の再現はありますか?
オマット

ウィジェットの幅/高さを取得するために動的にあなたが呼び出す必要があり.findRenderObejct()、その後と.sizeRenderBox box = widget.context.findRenderObject(); print(box.size);
RenanCoelho20年

ウィジェットのキーとしてGlobalKeyを渡してから、電話をかけることもできます_myKey.currentContext.findRenderObject()
Renan Coelho

1

そのためのウィジェットをあげましょう


class SizeProviderWidget extends StatefulWidget {
  final Widget child;
  final Function(Size) onChildSize;

  const SizeProviderWidget({Key key, this.onChildSize, this.child})
      : super(key: key);
  @override
  _SizeProviderWidgetState createState() => _SizeProviderWidgetState();
}

class _SizeProviderWidgetState extends State<SizeProviderWidget> {

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      widget.onChildSize(context.size);
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

ここで起こっていることは、どのウィジェットでも、ウィジェットがビルドされるとサイズ変数が使用可能になるため、ウィジェットがビルドされた直後に呼び出される関数を上記のウィジェットに渡すことで、ビルドされたウィジェットのサイズを使用できるようになります。

SizeProviderWidget(
 child:MyWidget(),//the widget we want the size of,
 onChildSize:(size){
  //the size of the rendered MyWidget() is available here
 }
)

EDIT だけでラップSizeProviderWidgetしてOrientationBuilder、それは、デバイスの向きを尊重するために


こんにちは!これは最初はうまく機能しているように見えましたが、1つの注意点を発見しました。それは、デバイスの向きを変更してもサイズが更新されなかったことです。ステートフルウィジェットの状態がデバイスのローテーションで再初期化されないためだと思います。
mattorb

HI、フラッタだけでウィジェット上にラップ、レゴのようなので、モジュール化されてOrientationBuilder任意の方向を尊重し始めるが、私は、デバイス母の平均
ヤドゥ

0

findRenderObject()は、RenderBox描画されたウィジェットのサイズを指定するために使用されるを返します。ウィジェットツリーの構築後に呼び出す必要があるため、何らかのコールバックメカニズムまたはaddPostFrameCallback()コールバックで使用する必要があります。

class SizeWidget extends StatefulWidget {
  @override
  _SizeWidgetState createState() => _SizeWidgetState();
}

class _SizeWidgetState extends State<SizeWidget> {
  final GlobalKey _textKey = GlobalKey();
  Size textSize;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => getSizeAndPosition());
  }

  getSizeAndPosition() {
    RenderBox _cardBox = _textKey.currentContext.findRenderObject();
    textSize = _cardBox.size;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Size Position"),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Text(
            "Currern Size of Text",
            key: _textKey,
            textAlign: TextAlign.center,
            style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
          ),
          SizedBox(
            height: 20,
          ),
          Text(
            "Size - $textSize",
            textAlign: TextAlign.center,
          ),
        ],
      ),
    );
  }
}

出力:

ここに画像の説明を入力してください


_textKey.currentContext.size十分です
RenanCoelho20年

0

パッケージを使用します:z_tools。手順:

1.メインファイルを変更します

void main() async {
  runZoned(
    () => runApp(
      CalculateWidgetAppContainer(
        child: Center(
          child: LocalizedApp(delegate, MyApp()),
        ),
      ),
    ),
    onError: (Object obj, StackTrace stack) {
      print('global exception: obj = $obj;\nstack = $stack');
    },
  );
}

2.機能で使用する

_Cell(
    title: 'cal: Column-min',
    callback: () async {
        Widget widget1 = Column(
        mainAxisSize: MainAxisSize.min,
        children: [
            Container(
            width: 100,
            height: 30,
            color: Colors.blue,
            ),
            Container(
            height: 20.0,
            width: 30,
            ),
            Text('111'),
        ],
        );
        // size = Size(100.0, 66.0)
        print('size = ${await getWidgetSize(widget1)}');
    },
),

0

最も簡単な方法は、実行時に子のサイズを計算するウィジェットであるMeasuredSizeを使用することです。

あなたはそれを次のように使うことができます:

MeasuredSize(
          onChange: (Size size) {
            setState(() {
              print(size);
            });
          },
          child: Text(
            '$_counter',
            style: Theme.of(context).textTheme.headline4,
          ),
        );

あなたはここでそれを見つけることができます:https//pub.dev/packages/measured_size


-2
<p>@override
<br/>
  Widget build(BuildContext context) {<br/>
   &nbsp;&nbsp; &nbsp;return Container(<br/>
     &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; width: MediaQuery.of(context).size.width,<br/>
     &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; height: MediaQuery.of(context).size.height,<br/>
    &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; color: Colors.blueAccent,<br/>
   &nbsp;&nbsp; &nbsp;);<br/>
  }</p>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.