1. ホーム
  2. flutter

[解決済み] Widgetの高さを取得するには?

2022-05-05 07:29:57

質問

の仕組みがわかりません。 LayoutBuilder は、Widgetの高さを取得するために使用されます。

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

非常に単純なケースですが、WidgetをLayoutBuilder.builderでラップしてStackに配置しようとしましたが、いつも次のようになります。 minHeight 0.0maxHeight INFINITY . LayoutBuilderの使い方が間違っているのでしょうか?

EDIT : LayoutBuilderはダメみたいですね。というのを発見しました。 CustomSingleChildLayout で、ほぼ解決しています。

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

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

ドキュメントにはこうも書いてある。

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

と、変な制約がありますね。

解決方法は?

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

ただ、一つ気をつけなければならないことがあります。ウィジェットがレンダリングされていない場合、そのコンテキストは存在しないかもしれません。そのため ListView ウィジェットは、潜在的に可視である場合にのみレンダリングされるからです。

もう一つの問題は、ウィジェットの RenderBox の間に build の呼び出しは、ウィジェットがまだレンダリングされていないためです。


しかし、ビルド中にサイズを取得する必要がある場合はどうすればいいのでしょうか! どうすればいいのでしょうか?

1つだけ、クールなウィジェットがあります。 Overlay とその OverlayEntry . これらはウィジェットを他のすべてのものの上に表示するために使用されます (スタックに似ています)。

しかし、最もクールなことは、それらが別の build のフローで構築されています。 通常のウィジェットです。

これには1つ、とてもクールな意味があるんです。 OverlayEntry は、実際のウィジェットツリーのウィジェットに依存する大きさを持つことができます。


しかし、OverlayEntryは手動で再構築する必要があるのではないでしょうか?

はい、そうです。でも、もうひとつ気をつけなければならないことがあります。 ScrollController に渡される Scrollable と同様のリスナーブルです。 AnimationController .

つまり AnimatedBuilderScrollController を使用すると、スクロール時にウィジェットを自動的に再構築する素敵な効果があります。このシチュエーションにぴったりでしょう?


すべてを組み合わせて例として

次の例では、ウィジェットに続くオーバーレイが 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();