Pour obtenir la taille/position d'un widget à l'écran, vous pouvez utiliser GlobalKey
pour obtenir son BuildContext
pour ensuite trouver le RenderBox
de ce widget spécifique, qui contiendra sa position globale et sa taille rendue.
Juste une chose à laquelle il faut faire attention : Ce contexte peut ne pas exister si le widget n'est pas rendu. Ce qui peut poser un problème avec ListView car les widgets ne sont rendus que s'ils sont potentiellement visibles.
Un autre problème est qu'il n'est pas possible d'obtenir le nom du widget. RenderBox
pendant 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 y a un widget sympa qui peut vous aider : Overlay
et son OverlayEntry
. Ils sont utilisés pour afficher les widgets au dessus de tout le reste (similaire à la pile).
Mais le plus cool, c'est qu'ils sont sur un autre site. build
flux ; ils sont construits après des widgets ordinaires.
Ça a une implication super cool : OverlayEntry
peut avoir une taille qui dépend des widgets de l'arbre de widgets actuel.
D'accord. Mais les OverlayEntry ne doivent-elles pas être reconstruites manuellement ?
Oui, ils le font. Mais il y a une autre chose dont il faut être conscient : ScrollController
transmis à un Scrollable
est une écoute similaire à AnimationController
.
Ce qui signifie que vous pouvez combiner un AnimatedBuilder
avec un ScrollController
cela aurait pour effet de reconstruire automatiquement votre widget lors d'un défilement. Parfait pour cette situation, non ?
En combinant le tout dans un exemple :
Dans l'exemple suivant, vous verrez une superposition qui suit un widget à l'intérieur de 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();
},
);
}
}
1 votes
LayoutBuilder vous donnera les contraintes de boîte du parent. Si vous voulez les tailles des enfants, vous devez utiliser une stratégie différente. Un exemple que je peux citer est le widget Wrap, qui effectue la mise en page en fonction de la taille de ses enfants dans la classe RenderWrap associée. Mais cela se produit pendant la mise en page, et non pendant la fonction build().
0 votes
@JonahWilliams Hmm. Je ne vois pas comment Wrap peut m'aider puisque c'est un widget conçu pour disposer les enfants autour (fonctionne quelque chose comme la grille flexbox du web). J'ai un widget enfant dont j'ai besoin de trouver la hauteur. Veuillez voir l'édition dans la question. J'ai presque résolu le problème avec CustomSingleChildLayout mais je suis resté bloqué sur ses limites.
0 votes
Pouvez-vous expliquer plus précisément ce que vous voulez ? Il existe de multiples solutions. Mais chacune a des cas d'utilisation différents.
0 votes
Bien sûr. Je suis en train de développer un paquet. L'utilisateur/développeur fournit un Widget à ma classe. Nous parlons ici de n'importe quel widget, de
new Text("hello")
à d'autres plus complexes. Je place ces widgets dans des ListView, et j'ai besoin de leur hauteur pour calculer certains effets de défilement. Je suis d'accord pour obtenir la hauteur au moment de la mise en page, comme le fait SingleChildLayoutDelegate.0 votes
Qu'entendez-vous par "effets de défilement" ? Avez-vous un exemple ?
0 votes
Quelque chose comme des listes de sticky-headers. J'ai besoin d'une hauteur de lignes pour calculer quelle ligne est en haut, à quelle distance se trouve la ligne suivante, et ensuite faire la translation de la ligne d'en-tête sur l'axe Y qui est dessiné en haut de la liste. J'ai déjà réalisé cela, mais avec la hauteur comme entrée. Je voudrais supprimer ce paramètre de hauteur, afin que l'utilisateur/développeur n'ait pas besoin de connaître la hauteur de chaque ligne.
0 votes
Il y a aussi la question ouverte à github.com/flutter/flutter/issues/16061
0 votes
Je pense que l'utilisation d'un CustomScrollView et de Slivers pourrait être une solution dans ce cas, mais je n'ai pas le temps de l'étudier. La seule chose est qu'il est implémenté pour avoir une seule AppBar en haut ; vous devriez écrire votre propre SliverHeader en suivant l'implémentation de SliverAppBar ou quelque chose comme ça. Le problème avec l'utilisation d'un CustomSingleChildLayout est qu'il dépend de la hauteur des enfants, et doit donc calculer la hauteur totale de la liste à chaque fois, ce qui peut être lent en fonction du nombre de widgets que vous rendez (et impose certaines contraintes sur ces widgets).