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
podczas build
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 jego OverlayEntry
. 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 a Scrollable
, jest podobna do słuchania AnimationController
.
Oznacza to, że możesz połączyć widżet AnimatedBuilder
z a ScrollController
, 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) {
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.
sticky.remove();