Jak uzyskać wysokość widżetu?

Nie rozumiem, jak LayoutBuilder jest używany do uzyskania wysokości widżetu.

Muszę wyświetlić listę widżetów i uzyskać ich wysokość, aby móc obliczyć specjalne efekty przewijania. Rozwijam pakiet i inni programiści dostarczają widget(nie kontroluję ich). Czytałem, że LayoutBuilder może być używany do uzyskania wysokości.

W bardzo prostym przypadku, próbowałem owinąć Widget w LayoutBuilder.budowniczego i umieścić go w stosie, ale zawsze dostaję minHeight 0.0, oraz maxHeight INFINITY. Czy źle wykorzystuję LayoutBuilder?

EDIT: wygląda na to, że LayoutBuilder nie działa. Znalazłem CustomSingleChildLayout który jest prawie rozwiązaniem.

Rozszerzyłem ten delegat i udało mi się uzyskać wysokość widgetu w metodzie getPositionForChild(Size size, Size childSize). Ale pierwszą wywołaną metodą jest Size getSize(BoxConstraints constraints) i jako ograniczenia dostaję 0 do nieskończoności, ponieważ układam te CustomSingleChildLayouts w widoku listy.

Moim problemem jest to, że SingleChildLayoutDelegate getSize działa tak, jakby musiał zwrócić wysokość widoku. Nie znam wzrostu dziecka w tym momencie. Mogę zwrócić tylko ograniczenia.najmniejsze (czyli 0, wysokość to 0), czyli ograniczenia.największy, który jest nieskończoność i zawiesza aplikację.

W dokumentach jest nawet napisane:

...ale wielkość rodzica nie może zależeć od wielkości dziecka.

I to jest dziwne ograniczenie.

Author: Amir_P, 2018-03-15

10 answers

Aby uzyskać rozmiar / pozycję widżetu na ekranie, możesz użyć GlobalKey, aby uzyskać jego BuildContext, aby następnie znaleźć RenderBox tego konkretnego widżetu, który będzie zawierał jego globalną pozycję i renderowany rozmiar.

Należy uważać na jedną rzecz: kontekst może nie istnieć, jeśli widżet nie zostanie renderowany. Co może powodować problem z ListView, ponieważ widżety są renderowane tylko wtedy, gdy są potencjalnie widoczne.

Innym problemem jest to, że nie można uzyskać widgetu RenderBox Podczas build wywołania jako widżet nie został jeszcze wyrenderowany.


ale potrzebuję rozmiaru podczas budowy! Co mogę zrobić?

Jest jeden fajny widget, który może pomóc: Overlay i jego OverlayEntry. Są one używane do wyświetlania widżetów na wszystko inne(podobnie do stosu).

Ale najfajniejsze jest to, że są na innym przepływie build; są zbudowane po zwykłych widgetach.

Które mają jedną super fajną implikację: OverlayEntry mogą mieć rozmiar zależny od widżetów rzeczywistego drzewa widżetów.


ok. Ale czy nakładka nie wymaga ręcznej przebudowy?

Tak, mają. Ale jest jeszcze jedna rzecz, o której należy pamiętać: ScrollController, przekazywana do Scrollable, jest listenable podobna do AnimationController.

Co oznacza, że można połączyć AnimatedBuilder z ScrollController, byłoby piękny efekt, aby odbudować widget automatycznie na przewijaniu. Idealny do tej sytuacji, prawda?


Łączenie wszystkiego w przykład:

W poniższym przykładzie zobaczysz nakładkę, która podąża za widżetem wewnątrz ListView i ma tę samą wysokość.

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();
      },
    );
  }
}

Uwaga :

Podczas przechodzenia do innego ekranu, wywołanie following w przeciwnym razie sticky pozostanie widoczne.

sticky.remove();
 164
Author: Rémi Rousselet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-10 09:59:24

Jest to (myślę) najprostszy sposób, aby to zrobić.

Skopiuj i wklej poniższe elementy do swojego projektu.

UPDATE: użycie RenderProxyBox powoduje nieco poprawniejszą implementację, ponieważ jest ona wywoływana przy każdej przebudowie dziecka i jego potomków, co nie zawsze ma miejsce w przypadku metody build () najwyższego poziomu.

Uwaga: nie jest to do końca skuteczny sposób, aby to zrobić, jak wskazał Hixie tutaj . Ale to najłatwiejsze.

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);
  }
}

Następnie, po prostu owinąć widżet, którego rozmiar chcesz zmierzyć MeasureSize.


var myChildSize = Size.zero;

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

Więc tak, wielkość rodzica nie może Może zależeć od wielkości dziecka, jeśli starasz się wystarczająco mocno.


Osobista anegdota-jest to przydatne przy ograniczaniu rozmiaru widżetów, takich jak Align, które lubią zajmować absurdalną ilość miejsca.

 51
Author: Dev Aggarwal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-31 17:21:28

Jeśli dobrze rozumiem, chcesz zmierzyć wymiar niektórych dowolnych widżetów i możesz owinąć te widżety innym widżetem. W takim przypadku, metoda w ta odpowiedź powinna działać dla Ciebie.

Zasadniczo rozwiązaniem jest związanie wywołania zwrotnego w cyklu życia widżetu, które zostanie wywołane po wyrenderowaniu pierwszej ramki, stamtąd można uzyskać dostęp do context.size. Haczyk polega na tym, że musisz owinąć widżet, który chcesz zmierzyć w widżecie stanowym. A jeśli absolutnie potrzebujesz rozmiaru w build(), wtedy możesz uzyskać do niego dostęp tylko w drugim renderowaniu (jest dostępny tylko po pierwszym renderowaniu).

 6
Author: Gan Quan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-26 00:29:01

Oto przykład, jak możesz użyć LayoutBuilder aby określić rozmiar widżetu.

Ponieważ widżet LayoutBuilder jest w stanie określić ograniczenia widżetu macierzystego, jednym z jego przypadków użycia jest możliwość dostosowania widżetów potomnych do wymiarów rodzica.

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),
      ),
    );
  }
}

Demo

demo

Logi

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
 6
Author: Omatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-03 12:33:59

Dam ci widget do tego


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;
  }
}

Tutaj dzieje się to, w każdym widgecie, zmienna rozmiaru będzie dostępna po zbudowaniu widgetu, więc przekazując funkcję do powyższego widgetu, który zostanie wywołany zaraz po zbudowaniu widgetu z wbudowanym rozmiarem widgetu, którego możesz użyć.

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

Edytuj Po prostu owinąć SizeProviderWidget z OrientationBuilder, aby uszanować orientację urządzenia

 2
Author: Yadu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-17 05:00:54

findRenderObject() zwraca RenderBox, który jest używany do podania rozmiaru rysowanego widżetu i powinien być wywołany po zbudowaniu drzewa widżetów, więc musi być używany z jakimś mechanizmem wywołania zwrotnego lub wywołania zwrotnego 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,
          ),
        ],
      ),
    );
  }
}

Wyjście:

Tutaj wpisz opis obrazka

 1
Author: jitsm555,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-05 18:49:15

Użyj pakietu: z_tools . Kroki:

1. Zmień plik główny

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. użyj w funkcji

_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
Author: qqzhao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-21 02:27:20

Najprostszym sposobem jest użycie MeasuredSize jest to widżet, który oblicza rozmiar dziecka w trybie runtime.

Możesz go używać tak:

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

Znajdziesz go tutaj: https://pub.dev / packages / measured_size

 0
Author: Ayham Orfali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-19 13:37:49

To proste i nadal można to zrobić w StatelessWidget.

class ColumnHeightWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final scrollController = ScrollController();
    final columnKey = GlobalKey();

    _scrollToCurrentProgress(columnKey, scrollController);

    return Scaffold(
      body: SingleChildScrollView(
        controller: scrollController,
        child: Column(
          children: [],
        ),
      ),
    );
  }

  void _scrollToCurrentProgress(GlobalKey<State<StatefulWidget>> columnKey,
      ScrollController scrollController) {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      final RenderBox renderBoxRed =
          columnKey.currentContext.findRenderObject();
      final height = renderBoxRed.size.height;
      scrollController.animateTo(percentOfHeightYouWantToScroll * height,
          duration: Duration(seconds: 1), curve: Curves.decelerate);
    });
  }
}

W ten sam sposób można obliczyć wysokość dziecka widżetu i przewijać do tej pozycji.

 0
Author: Drashyr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-21 11:54:27
<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>
 -2
Author: Zaw Htet Aung,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-08 15:03:02