Để có kích thước / vị trí của một tiện ích con trên màn hình, bạn có thể sử dụng GlobalKeyđể lấy nó BuildContextđể sau đó tìm RenderBoxtiện ích cụ thể đó, sẽ chứa vị trí chung và kích thước hiển thị của nó.
Chỉ cần cẩn thận một điều: Ngữ cảnh đó có thể không tồn tại nếu tiện ích con không được hiển thị. Điều này có thể gây ra sự cố ListViewvì các tiện ích con chỉ được hiển thị nếu chúng có khả năng hiển thị.
Một vấn đề khác là bạn không thể nhận tiện ích con RenderBoxtrong khi buildgọi vì tiện ích con chưa được hiển thị.
Nhưng tôi cần kích thước trong quá trình xây dựng! Tôi có thể làm gì?
Có một tiện ích tuyệt vời có thể giúp: Overlayvà của nó OverlayEntry. Chúng được sử dụng để hiển thị các widget trên đầu mọi thứ khác (tương tự như ngăn xếp).
Nhưng điều thú vị nhất là chúng ở một builddòng chảy khác ; chúng được xây dựng sau các vật dụng thông thường.
Điều đó có một ngụ ý cực kỳ thú vị: OverlayEntrycó thể có kích thước phụ thuộc vào các widget của cây widget thực tế.
Được chứ. Nhưng OverlayEntry không yêu cầu được xây dựng lại theo cách thủ công?
Có, họ có. Nhưng có một điều khác cần lưu ý:, được ScrollControllerchuyển cho a Scrollable, là một tương tự có thể nghe được AnimationController.
Có nghĩa là bạn có thể kết hợp một AnimatedBuildervới một ScrollController, nó sẽ có hiệu ứng đáng yêu để xây dựng lại tiện ích con của bạn tự động trên một cuộn. Hoàn hảo cho tình huống này, phải không?
Kết hợp mọi thứ thành một ví dụ:
Trong ví dụ sau, bạn sẽ thấy một lớp phủ theo sau một tiện ích con bên trong ListViewvà có cùng chiều cao.
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();
},
);
}
}
Ghi chú :
Khi điều hướng đến một màn hình khác, cuộc gọi sau nếu không dính sẽ vẫn hiển thị.
sticky.remove();