Để 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 RenderBox
tiệ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ố ListView
vì 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 RenderBox
trong khi build
gọ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: Overlay
và 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 build
dò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ị: OverlayEntry
có 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 ScrollController
chuyể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 AnimatedBuilder
vớ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 ListView
và 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();