web-dev-qa-db-fra.com

Comment obtenir la hauteur d'un widget?

Je ne comprends pas comment LayoutBuilder est utilisé pour obtenir la hauteur d'un widget.

Je dois afficher la liste des widgets et obtenir leur hauteur pour pouvoir calculer des effets de défilement spéciaux. Je développe un paquet et d'autres développeurs fournissent un widget (je ne les contrôle pas). J'ai lu que LayoutBuilder peut être utilisé pour obtenir de la hauteur.

Dans des cas très simples, j’ai essayé d’envelopper Widget dans LayoutBuilder.builder et de le placer dans la pile, mais j’obtiens toujours minHeight0.0 Et maxHeightINFINITY. Est-ce que j'utilise mal le LayoutBuilder?

[~ # ~] éditer [~ # ~] : Il semble que LayoutBuilder soit une solution de rechange. J'ai trouvé le CustomSingleChildLayout qui est presque une solution.

J'ai étendu ce délégué et j'ai pu obtenir la hauteur du widget dans la méthode getPositionForChild(Size size, Size childSize). MAIS, la première méthode appelée est Size getSize(BoxConstraints constraints) et comme contrainte, je reçois 0 pour INFINITY car je pose ces CustomSingleChildLayouts dans un ListView.

Mon problème est que SingleChildLayoutDelegate getSize fonctionne comme il est nécessaire pour renvoyer la hauteur d'une vue. Je ne connais pas la taille d'un enfant à ce moment. Je ne peux que renvoyer des contraintes.smallest (qui vaut 0, la hauteur est 0), ou des contraintes.biggest qui est infini et qui bloque l'application.

Dans la documentation, on dit même:

... mais la taille du parent ne peut pas dépendre de la taille de l'enfant.

Et c'est une limitation étrange.

31
Gudin

Pour obtenir la taille/la position d’un widget à l’écran, vous pouvez utiliser GlobalKey pour obtenir son BuildContext afin de trouver ensuite le RenderBox de ce widget spécifique, qui contiendra son position et taille rendue.

Une chose à laquelle il faut faire attention: ce contexte peut ne pas exister si le widget n'est pas rendu. Cela peut poser un problème avec ListView car les widgets ne sont rendus que s'ils sont potentiellement visibles.

Un autre problème est que vous ne pouvez pas obtenir le RenderBox d'un widget pendant l'appel de build car le widget n'a pas encore été rendu.


Mais j'ai besoin de la taille pendant la construction! Que puis-je faire?

Il existe un widget intéressant qui peut aider: Overlay et son OverlayEntry. Ils sont utilisés pour afficher des widgets par-dessus tout le reste (similaire à stack).

Mais le plus cool, c’est qu’ils sont sur un autre build flux; ils sont construits après widgets réguliers.

Cela a une implication super cool: OverlayEntry peut avoir une taille qui dépend des widgets de l’arborescence des widgets.


d'accord. Mais OverlayEntry ne nécessite-t-il pas une reconstruction manuelle?

Oui, ils le font. Mais il y a une autre chose à savoir: ScrollController, passé à un Scrollable, est un écoutable similaire à AnimationController.

Ce qui signifie que vous pourriez combiner un AnimatedBuilder avec un ScrollController, cela aurait le bel effet de reconstruire votre widget automatiquement sur un parchemin. Parfait pour cette situation, non?


Tout combiner dans un exemple:

Dans l'exemple suivant, vous verrez une superposition qui suit un widget dans ListView et partage la même hauteur.

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(
      opaque: false,
      // lambda created to help working with hot-reload
      builder: (context) => stickyBuilder(context),
    );

    // not possible inside initState
    SchedulerBinding.instance.addPostFrameCallback((_) {
      Overlay.of(context).insert(sticky);
    });
    super.initState();
  }

  @override
  void dispose() {
    // remove possible overlays on dispose as they would be visible even after [Navigator.Push]
    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();
      },
    );
  }
}
52
Rémi Rousselet

Si je comprends bien, vous souhaitez mesurer la dimension de certains widgets arbitraires et vous pouvez envelopper ces widgets avec un autre widget. Dans ce cas, la méthode entre cette réponse devrait fonctionner pour vous.

Fondamentalement, la solution consiste à lier un rappel dans le cycle de vie du widget, qui sera appelé après le rendu de la première image. À partir de là, vous pourrez accéder à context.size. Le problème, c'est que vous devez envelopper le widget que vous souhaitez mesurer dans un widget avec état. Et, si vous avez absolument besoin de la taille dans build(), vous ne pourrez y accéder que lors du deuxième rendu (disponible uniquement après le premier rendu).

0
Gan Quan