Ich verstehe nicht, wie LayoutBuilder
die Höhe eines Widgets ermittelt wird.
Ich muss die Liste der Widgets anzeigen und deren Höhe ermitteln, damit ich einige spezielle Bildlaufeffekte berechnen kann. Ich entwickle ein Paket und andere Entwickler stellen Widgets zur Verfügung (ich kontrolliere sie nicht). Ich habe gelesen, dass LayoutBuilder verwendet werden kann, um die Höhe zu ermitteln.
In einem sehr einfachen Fall habe ich versucht, Widget in LayoutBuilder.builder zu verpacken und in den Stapel zu legen, aber ich bekomme immer minHeight
0.0
und maxHeight
INFINITY
. Missbrauche ich den LayoutBuilder?
EDIT : Es scheint, dass LayoutBuilder ein No Go ist. Ich habe das CustomSingleChildLayout gefunden, das fast eine Lösung ist.
Ich habe diesen Delegaten erweitert und konnte die Höhe des Widgets in der getPositionForChild(Size size, Size childSize)
Methode ermitteln. ABER die erste Methode, die aufgerufen wird, ist Size getSize(BoxConstraints constraints)
und als Einschränkung erhalte ich 0 bis INFINITY, weil ich diese CustomSingleChildLayouts in eine ListView lege.
Mein Problem ist, dass SingleChildLayoutDelegate so getSize
funktioniert, als ob es die Höhe einer Ansicht zurückgeben müsste . Ich kenne die Größe eines Kindes in diesem Moment nicht. Ich kann nur Constraints.klein (das ist 0, die Höhe ist 0) oder Constraints.biggest, das unendlich ist und die App zum Absturz bringt, zurückgeben.
In den Dokumenten steht sogar:
... aber die Größe des Elternteils kann nicht von der Größe des Kindes abhängen.
Und das ist eine seltsame Einschränkung.
new Text("hello")
komplexeren. Ich lege diese Widgets in ListView und benötige ihre Höhe, um einige Bildlaufeffekte zu berechnen. Ich bin damit einverstanden, die Höhe zum Layoutzeitpunkt zu ermitteln, genau wie SingleChildLayoutDelegate.Antworten:
Um die Größe / Position eines Widgets auf dem Bildschirm anzuzeigen, können Sie es verwenden
GlobalKey
, um esBuildContext
zu ermitteln und dann das zu findenRenderBox
dieses spezielle Widget, das seine globale Position und gerenderte Größe enthält.Nur eines ist zu beachten: Dieser Kontext existiert möglicherweise nicht, wenn das Widget nicht gerendert wird. Dies kann zu Problemen führen,
ListView
da Widgets nur gerendert werden, wenn sie möglicherweise sichtbar sind.Ein weiteres Problem ist, dass Sie
RenderBox
während einesbuild
Anrufs keine Widgets erhalten können , da das Widget noch nicht gerendert wurde.Aber ich muss die Größe während des Builds! Was kann ich tun?
Es gibt ein cooles Widget, das helfen kann:
Overlay
und seinOverlayEntry
. Sie werden verwendet, um Widgets über allem anderen anzuzeigen (ähnlich wie beim Stapel).Aber das Coolste ist, dass sie sich in einem anderen
build
Fluss befinden. Sie werden nach regulären Widgets erstellt.Das hat eine super coole Implikation:
OverlayEntry
Kann eine Größe haben, die von den Widgets des tatsächlichen Widget-Baums abhängt.In Ordnung. Aber muss OverlayEntry nicht manuell neu erstellt werden?
Ja, das tun sie. Aber es gibt noch eine andere Sache, die Sie beachten sollten:
ScrollController
Übergeben an aScrollable
, ist eine hörbare ähnlich wieAnimationController
.Das heißt, Sie könnten ein
AnimatedBuilder
mit einem kombinierenScrollController
, es hätte den schönen Effekt, Ihr Widget automatisch auf einer Schriftrolle neu zu erstellen. Perfekt für diese Situation, oder?Alles zu einem Beispiel zusammenfassen:
Im folgenden Beispiel sehen Sie eine Überlagerung, die einem Widget im Inneren folgt
ListView
und dieselbe Höhe aufweist.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(); }, ); } }
Hinweis :
Wenn Sie zu einem anderen Bildschirm navigieren, bleibt der folgende Anruf sichtbar, da er sonst sichtbar bleibt.
quelle
GlobalKey
. Gute Antwort.Rect
ListItem eines zu kennen,ListView.builder
wenn es gedrückt wird ? Wäre es ein Overkill,GlobalKey listItemKey = GlobalKey();
für jedes listItem zu setzen? Angenommen, ich habe +10000 Artikel. Gibt es eine clevere Möglichkeit, dies ohne Leistungs- / Speicherprobleme zu verwalten?Dies ist (glaube ich) der einfachste Weg, dies zu tun.
Kopieren Sie Folgendes in Ihr Projekt.
UPDATE: Die Verwendung
RenderProxyBox
führt zu einer etwas korrekteren Implementierung, da sie bei jeder Neuerstellung des untergeordneten Elements und seiner Nachkommen aufgerufen wird, was bei der build () -Methode der obersten Ebene nicht immer der Fall ist.HINWEIS: Dies ist nicht gerade ein effizienter Weg, wie Hixie hier ausgeführt hat . Aber es ist am einfachsten.
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); } }
Wickeln Sie dann einfach das Widget ein, mit dessen Größe Sie messen möchten
MeasureSize
.var myChildSize = Size.zero; Widget build(BuildContext context) { return ...( child: MeasureSize( onChange: (size) { setState(() { myChildSize = size; }); }, child: ... ), ); }
Also ja, die Größe des Elternteils
kann nichtkann auf die Größe des Kindes abhängen , wenn man hart genug versuchen.Persönliche Anekdote - Dies ist praktisch, um die Größe von Widgets wie zu beschränken
Align
, die gerne absurd viel Platz beanspruchen.quelle
PreferredSizeWidget
,preferredSize
wird nur einmal aufgerufen, so dass Sie nicht die Höhe in einer einfachen Art und Weise einstellen.Wenn ich das richtig verstehe, möchten Sie die Dimension einiger beliebiger Widgets messen und diese Widgets mit einem anderen Widget umschließen. In diesem Fall sollte die Methode in dieser Antwort für Sie funktionieren.
Grundsätzlich besteht die Lösung darin, einen Rückruf im Widget-Lebenszyklus zu binden, der nach dem Rendern des ersten Frames aufgerufen wird. Von dort aus können Sie darauf zugreifen
context.size
. Der Haken ist, dass Sie das Widget, das Sie messen möchten, in ein zustandsbehaftetes Widget einschließen müssen. Und wenn Sie die Größe innerhalb unbedingt benötigenbuild()
, können Sie nur im zweiten Render darauf zugreifen (sie ist erst nach dem ersten Render verfügbar).quelle
Hier ist ein Beispiel, wie Sie
LayoutBuilder
die Größe des Widgets bestimmen können.Da das
LayoutBuilder
Widget in der Lage ist, die Einschränkungen des übergeordneten Widgets zu bestimmen, besteht einer seiner Anwendungsfälle darin, dass die untergeordneten Widgets an die Dimensionen des übergeordneten Widgets angepasst werden können.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
Protokolle
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
quelle
findRenderObject()
GibtRenderBox
das zurück, mit dem die Größe des gezeichneten Widgets angegeben wird. Es sollte aufgerufen werden, nachdem der Widget-Baum erstellt wurde. Daher muss es mit einem Rückrufmechanismus oderaddPostFrameCallback()
Rückrufen verwendet werden.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, ), ], ), ); } }
Ausgabe:
quelle
Verwenden Sie das Paket: z_tools . Die Schritte:
1. Hauptdatei ändern
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. Verwendung in Funktion
_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)}'); }, ),
quelle