Щоб отримати розмір / позицію віджета на екрані, ви можете скористатися GlobalKeyдля його отримання, BuildContextщоб потім знайти RenderBoxтой конкретний віджет, який буде містити його глобальну позицію та розмір відтворення.
Будьте обережні лише з одним: цього контексту може не існувати, якщо віджет не відображається. Що може спричинити проблему, ListViewоскільки віджети відображаються лише в тому випадку, якщо вони потенційно видимі.
Інша проблема полягає в тому, що ви не можете отримати віджет RenderBoxпід час buildдзвінка, оскільки віджет ще не відтворений.
Але мені потрібен розмір під час збірки! Що я можу зробити?
Є один класний віджет, який може допомогти: Overlayі його OverlayEntry. Вони використовуються для відображення віджетів поверх усього іншого (подібно до стека).
Але найкрутіше те, що вони йдуть іншим buildпотоком; вони будуються за звичайними віджетами.
Це має одне надзвичайно приємне значення: OverlayEntryможе мати розмір, який залежить від віджетів власне дерева віджетів.
Добре. Але чи не потрібно OverlayEntry перебудовувати вручну?
Так, вони це роблять. Але слід пам’ятати ще про одну річ: ScrollControllerпередана до Scrollable, є послухою, схожою на AnimationController.
Це означає, що ви можете поєднати а AnimatedBuilderз ScrollController, це матиме чудовий ефект для автоматичного відновлення віджету автоматично на прокрутці. Ідеально підходить для цієї ситуації, так?
Поєднуючи все в приклад:
У наступному прикладі ви побачите накладення, яке слідує за віджетом всередині ListViewта має однакову висоту.
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();
},
);
}
}
Примітка :
Під час навігації на інший екран дзвінок після інакшого липкого залишатиметься видимим.
sticky.remove();