Ответ 1
Чтобы получить размер/положение виджета на экране, вы можете использовать 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(
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();
},
);
}
}