Щоб отримати розмір / позицію віджета на екрані, ви можете скористатися 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();