No entiendo cómo LayoutBuilder
se usa para obtener la altura de un widget.
Necesito mostrar la lista de widgets y obtener su altura para poder calcular algunos efectos especiales de desplazamiento. Estoy desarrollando un paquete y otros desarrolladores proporcionan un widget (no los controlo). Leí que LayoutBuilder se puede usar para obtener altura.
En un caso muy simple, traté de envolver Widget en LayoutBuilder.builder y ponerlo en Stack, pero siempre obtengo minHeight
0.0
, y maxHeight
INFINITY
. ¿Estoy haciendo un mal uso de LayoutBuilder?
EDITAR : Parece que LayoutBuilder no es posible. Encontré CustomSingleChildLayout, que es casi una solución.
Extendí ese delegado y pude obtener la altura del widget en el getPositionForChild(Size size, Size childSize)
método. PERO, el primer método que se llama es Size getSize(BoxConstraints constraints)
y, como restricciones, obtengo 0 a INFINITY porque estoy colocando estos CustomSingleChildLayouts en un ListView.
Mi problema es que SingleChildLayoutDelegate getSize
funciona como si necesitara devolver la altura de una vista. No sé la altura de un niño en ese momento. Solo puedo devolver restricints.smallest (que es 0, la altura es 0), o constraints.biggest que es infinito y bloquea la aplicación.
En los documentos incluso dice:
... pero el tamaño del padre no puede depender del tamaño del niño.
Y esa es una limitación extraña.
new Text("hello")
más complejos. Coloco estos widgets en ListView y necesito su altura para calcular algunos efectos de desplazamiento. Estoy de acuerdo con obtener la altura en el momento del diseño, al igual que lo que hace SingleChildLayoutDelegate.Respuestas:
Para obtener el tamaño / posición de un widget en la pantalla, puede usar
GlobalKey
para obtenerloBuildContext
y luego encontrar elRenderBox
de ese widget específico, que contendrá su posición global y tamaño renderizado.Solo una cosa a tener en cuenta: ese contexto puede no existir si el widget no se representa. Lo que puede causar un problema
ListView
ya que los widgets se procesan solo si son potencialmente visibles.Otro problema es que no puede obtener un widget
RenderBox
durante labuild
llamada, ya que aún no se ha renderizado.¡Pero necesito el tamaño durante la construcción! ¿Que puedo hacer?
Hay un widget genial que puede ayudar:
Overlay
y esOverlayEntry
. Se utilizan para mostrar widgets encima de todo lo demás (similar a la pila).Pero lo mejor es que están en un
build
flujo diferente ; se construyen después de los widgets normales.Eso tiene una implicación genial:
OverlayEntry
puede tener un tamaño que depende de los widgets del árbol de widgets real.Bueno. Pero, ¿no es necesario reconstruir OverlayEntry manualmente?
Ellos si. Pero hay otra cosa a tener en cuenta:,
ScrollController
pasado aScrollable
, es un similar escuchable aAnimationController
.Lo que significa que podría combinar un
AnimatedBuilder
con unScrollController
, tendría el efecto encantador de reconstruir su widget automáticamente en un pergamino. Perfecto para esta situación, ¿verdad?Combinando todo en un ejemplo:
En el siguiente ejemplo, verá una superposición que sigue a un widget en el interior
ListView
y comparte la misma altura.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(); }, ); } }
Nota :
Cuando navegue a una pantalla diferente, llame a seguir, de lo contrario, permanecerá visible.
fuente
GlobalKey
. Gran respuesta.Rect
de cualquier ListItem de unListView.builder
cuando se presionan? ¿Sería una exageración establecerloGlobalKey listItemKey = GlobalKey();
para cada listItem? Digamos que tengo más de 10000 artículos. ¿Existe una forma inteligente de gestionar esto sin problemas de rendimiento / memoria?Esta es (creo) la forma más sencilla de hacer esto.
Copie y pegue lo siguiente en su proyecto.
ACTUALIZACIÓN: el uso
RenderProxyBox
da como resultado una implementación un poco más correcta, porque se llama en cada reconstrucción del hijo y sus descendientes, lo que no siempre es el caso del método build () de nivel superior.NOTA: Esta no es exactamente una forma eficiente de hacer esto, como lo señala Hixie aquí . Pero es el más sencillo.
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); } }
Luego, simplemente envuelva el widget cuyo tamaño le gustaría medir
MeasureSize
.var myChildSize = Size.zero; Widget build(BuildContext context) { return ...( child: MeasureSize( onChange: (size) { setState(() { myChildSize = size; }); }, child: ... ), ); }
Entonces, sí, el tamaño del padre
nopuede depender del tamaño del niño si se esfuerza lo suficiente.Anécdota personal: esto es útil para restringir el tamaño de los widgets como
Align
, que les gusta ocupar una cantidad absurda de espacio.fuente
PreferredSizeWidget
,preferredSize
solo se llama una vez, por lo que no puede establecer la altura de forma sencilla.Si lo entiendo correctamente, desea medir la dimensión de algunos widgets arbitrarios y puede envolver esos widgets con otro widget. En ese caso, el método en esta respuesta debería funcionar para usted.
Básicamente, la solución es vincular una devolución de llamada en el ciclo de vida del widget, que se llamará después de que se procese el primer fotograma, desde allí puede acceder
context.size
. El problema es que debe ajustar el widget que desea medir dentro de un widget con estado. Y, si es absolutamente necesario el tamaño dentrobuild()
, solo puede acceder a él en el segundo render (solo está disponible después del primer render).fuente
Aquí hay una muestra de cómo se puede utilizar
LayoutBuilder
para determinar el tamaño del widget.Dado que el
LayoutBuilder
widget puede determinar las restricciones de su widget principal, uno de sus casos de uso es poder hacer que sus widgets secundarios se adapten a las dimensiones de sus principales.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), ), ); } }
Manifestación
Registros
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
fuente
findRenderObject()
devuelve elRenderBox
que se usa para dar el tamaño del widget dibujado y debe ser llamado después de que se construya el árbol de widgets, por lo que debe usarse con algún mecanismo deaddPostFrameCallback()
devolución de llamada o devoluciones de llamada.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, ), ], ), ); } }
Salida:
fuente
use el paquete: z_tools . Los pasos:
1. cambiar el archivo principal
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. uso en función
_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)}'); }, ),
fuente