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, a inni programiści zapewniają widżet (nie kontroluję ich). Czytałem, że LayoutBuilder może służyć do uzyskania wysokości.
W bardzo prostym przypadku próbowałem zawinąć Widget w LayoutBuilder.builder i umieścić go w stosie, ale zawsze otrzymuję minHeight
0.0
i maxHeight
INFINITY
. Czy nadużywam LayoutBuilder?
EDYCJA : Wygląda na to, że LayoutBuilder nie ma wyjścia. Znalazłem CustomSingleChildLayout, który jest prawie rozwiązaniem.
Rozszerzyłem tego delegata i udało mi się uzyskać wysokość widżetu w getPositionForChild(Size size, Size childSize)
metodzie. ALE, pierwsza wywoływana metoda to Size getSize(BoxConstraints constraints)
i jako ograniczenie, otrzymuję 0 do INFINITY, ponieważ układam te CustomSingleChildLayouts w ListView.
Mój problem polega na tym, że SingleChildLayoutDelegate getSize
działa tak, jakby zwracał wysokość widoku. W tej chwili nie znam wzrostu dziecka. Mogę zwrócić tylko constraints.smallest (czyli 0, wysokość to 0) lub constraints.biggest, co oznacza nieskończoność i powoduje awarię aplikacji.
W dokumentach jest nawet napisane:
... ale wielkość rodzica nie może zależeć od wzrostu dziecka.
I to jest dziwne ograniczenie.
new Text("hello")
bardziej złożonych. Umieszczam te widżety w ListView i potrzebuję ich wysokości, aby obliczyć niektóre efekty przewijania. Mogę uzyskać wysokość w czasie układu, tak jak robi to SingleChildLayoutDelegate.Odpowiedzi:
Aby uzyskać rozmiar / pozycję widżetu na ekranie, możesz użyć
GlobalKey
go,BuildContext
aby następnie znaleźćRenderBox
ten konkretny widżet, który będzie zawierał jego globalną pozycję i renderowany rozmiar.Tylko jedna rzecz, na którą należy uważać: ten kontekst może nie istnieć, jeśli widżet nie jest renderowany. Co może powodować problem z
ListView
widżetami as jest renderowany tylko wtedy, gdy są one potencjalnie widoczne.Innym problemem jest to, że nie możesz pobrać widżetu
RenderBox
podczasbuild
połączenia, ponieważ 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 jegoOverlayEntry
. Służą do wyświetlania widżetów na wszystkim innym (podobnie do stosu).Ale najfajniejsze jest to, że są na innym
build
nurcie; są tworzone na podstawie zwykłych widgetów.Ma to jedną super fajną konsekwencję:
OverlayEntry
może mieć rozmiar zależny od widżetów w aktualnym drzewie widżetów.W porządku. Ale czy OverlayEntry nie wymaga ręcznej przebudowy?
Tak, robią. Ale jest jeszcze jedna rzecz, o której należy pamiętać:
ScrollController
przekazana do aScrollable
, jest podobna do słuchaniaAnimationController
.Oznacza to, że możesz połączyć widżet
AnimatedBuilder
z aScrollController
, miałoby cudowny efekt, gdybyś automatycznie przebudował swój widżet na zwoju. Idealny w tej sytuacji, prawda?Łącząc wszystko 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 śledzone w przeciwnym razie pozostanie widoczne.
źródło
GlobalKey
. Świetna odpowiedź.Rect
dowolne ListItem aListView.builder
kiedy są wciśnięte. Czy ustawienieGlobalKey listItemKey = GlobalKey();
dla każdego elementu listItem byłoby przesadą? Powiedzmy, że mam +10000 przedmiotów. Czy istnieje sprytny sposób radzenia sobie z tym bez problemów z wydajnością / pamięcią?Jest to (myślę) najprostszy sposób, aby to zrobić.
Skopiuj i wklej następujące elementy do swojego projektu.
AKTUALIZACJA: użycie
RenderProxyBox
skutkuje nieco poprawniejszą implementacją, ponieważ jest wywoływana przy każdej przebudowie dziecka i jego potomków, co nie zawsze ma miejsce w przypadku metody build () najwyższego poziomu.UWAGA: To nie jest dokładnie to skuteczny sposób na to, jak wskazał przez 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 zawiń widżet, którego rozmiar chcesz mierzyć
MeasureSize
.var myChildSize = Size.zero; Widget build(BuildContext context) { return ...( child: MeasureSize( onChange: (size) { setState(() { myChildSize = size; }); }, child: ... ), ); }
Więc tak, wielkość rodzica
niemoże zależeć od wielkości dziecka, jeśli będziesz wystarczająco się starać.Osobista anegdota - przydaje się przy ograniczaniu rozmiaru widżetów, takich jak ten
Align
, który lubi zajmować absurdalną ilość miejsca.źródło
PreferredSizeWidget
,preferredSize
jest wywoływana tylko raz, więc nie można łatwo ustawić wysokości.Jeśli dobrze rozumiem, chcesz zmierzyć rozmiar niektórych dowolnych widżetów i możesz owinąć te widżety innym widżetem. W takim przypadku metoda podana w tej odpowiedzi powinna działać dla Ciebie.
Zasadniczo rozwiązaniem jest powiązanie wywołania zwrotnego w cyklu życia widgetu, które zostanie wywołane po wyrenderowaniu pierwszej klatki, z którego można uzyskać dostęp
context.size
. Problem polega na tym, że widżet, który chcesz zmierzyć, musisz opakować w widżet stanowy. A jeśli absolutnie potrzebujesz rozmiaru w ramachbuild()
, możesz uzyskać do niego dostęp tylko w drugim renderowaniu (jest dostępny tylko po pierwszym renderowaniu).źródło
Oto przykład, jak
LayoutBuilder
określić rozmiar widżetu.Ponieważ
LayoutBuilder
widget jest w stanie określić ograniczenia swojego widgetu nadrzędnego, jednym z jego przypadków użycia jest możliwość dostosowania widgetów podrzędnych do wymiarów ich 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), ), ); } }
Próbny
Dzienniki
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
źródło
.findRenderObejct()
a następnie.size
.RenderBox box = widget.context.findRenderObject(); print(box.size);
_myKey.currentContext.findRenderObject()
Pozwól, że dam ci do tego widget
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ę tak, że w każdym Widgecie zmienna rozmiaru będzie dostępna po zbudowaniu widgetu, więc przekazując funkcję do powyższego widgetu, która zostanie wywołana zaraz po zbudowaniu widgetu z rozmiarem zbudowanego 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 } )
EDIT Wystarczy owinąć
SizeProviderWidget
sięOrientationBuilder
, aby respektować orientację urządzeniaźródło
OrientationBuilder
który zaczyna szanować każdą orientację, mam na myśli urządzenie hahafindRenderObject()
zwraca wartość,RenderBox
która jest używana do podania rozmiaru rysowanego widgetu i powinna być wywoływana po zbudowaniu drzewa widgetów, więc musi być używana z jakimś mechanizmemaddPostFrameCallback()
wywołania zwrotnego lub wywołaniami zwrotnymi.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, ), ], ), ); } }
Wynik:
źródło
_textKey.currentContext.size
wystarczyuż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żywać 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)}'); }, ),
źródło
Najłatwiej jest użyć MeasuredSize , czyli widgetu, który oblicza rozmiar swojego elementu podrzędnego w czasie wykonywania.
Możesz go używać w ten sposób:
MeasuredSize( onChange: (Size size) { setState(() { print(size); }); }, child: Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), );
Możesz go znaleźć tutaj: https://pub.dev/packages/measured_size
źródło
<p>@override <br/> Widget build(BuildContext context) {<br/> return Container(<br/> width: MediaQuery.of(context).size.width,<br/> height: MediaQuery.of(context).size.height,<br/> color: Colors.blueAccent,<br/> );<br/> }</p>
źródło