什么是addPostFrameCallback
addPostFrameCallback方法是Flutter中WidgetsBinding类的一部分,该类从SchedulerBinding继承而来。它允许使用者注册一个回调,这个回调将在当前帧渲染完毕后被调用。这个回调接收一个时间戳参数,表示调用它的时间。
实践部分
接下来我们探讨两个示例,如下所示:
1.创建一个在页面渲染完成后显示的对话框,并确保 setState 不会重新渲染该对话框。
2. 获取创建的小部件的尺寸。
1.第一个示例
Dialog出现一次后就永远消失了,任凭button呼叫setState
以下是整个代码块:
class ExplorePage extends StatefulWidget {
const ExplorePage({super.key});
@override
State<ExplorePage> createState() => _DialogPageState();
}
class _DialogPageState extends State<ExplorePage> {
void _showDialog(BuildContext context) {
showDialog(
context: context,
barrierDismissible: true,
builder: (context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 100),
child: const Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(
Icons.emoji_emotions,
color: Colors.yellow,
size: 50,
),
Text("哈哈,现在button永远也找不到我了",
style: TextStyle(fontSize: 16),
textAlign: TextAlign.center,
)
],
),
),
);
});
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_showDialog(context);
});
}
@override
Widget build(BuildContext context) {
return Center(
child: InkWell(
onTap: () {
setState(() {});
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 30,vertical: 20),
decoration: const BoxDecoration(
color: Colors.teal,
borderRadius: BorderRadius.all(Radius.circular(6))),
child: const Text(
"呼叫 Dialog",
style: TextStyle(fontSize: 16, color: Colors.black),
textAlign: TextAlign.center,
),
),
),
);
}
}
关键就在initState中:
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_showDialog(context);
});
}
我们在这里注册了一个帧渲染完毕时的回调,一旦这个回调被触发,这个部件就会拥有关于自身上下文的全部信息,这进一步包含了部件的布局信息(我们将在第二个例子中看到如何提取这些信息)。这个方法的文档部分描述如下:
Schedule a callback for the end of this frame.(安排一个在本帧结束时的回调 )
Does not request a new frame. (不会请求新的帧)
This callback is run during a frame, just after the persistent frame callbacks (which is when the main rendering pipeline has been flushed). If a frame is in progress and post-frame callbacks haven’t been executed yet, then the registered callback is still executed during the frame. Otherwise, the registered callback is executed during the next frame.(这个回调会在固定帧回调之后的单帧期间运行(这是主要的渲染管道已经被清空的时候)。如果单帧正在进程中并且帧后回调还没被执行,那么这个已注册的回调仍会在该帧执行。否则,已注册的回调将在下一帧执行。 回调会按照它们被添加的顺序执行。 帧后回调无法被注销。它们只会被调用一次)
2.获取部件的尺寸
addPostFrameCallback另一个用途是执行计算或测量:如果你需要在帧渲染后计算或测量UI元素的某些属性,如它们的大小或位置,你可以使用addPostFrameCallback来安排代码的执行。
以下是示例代码:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: WidgetMeasurements(),
);
}
}
class WidgetMeasurements extends StatefulWidget {
const WidgetMeasurements({super.key});
@override
State<WidgetMeasurements> createState() => _WidgetMeasurementsState();
}
class _WidgetMeasurementsState extends State<WidgetMeasurements> {
final GlobalKey textKey = GlobalKey();
Size size = const Size(0, 0);
Offset offset = const Offset(0, 0);
@override
void initState() {
super.initState();
getDimensions();
}
void getDimensions() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
getSizeAndPosition();
});
}
getSizeAndPosition() {
RenderBox logoBox = textKey.currentContext!.findRenderObject() as RenderBox;
size = logoBox.size;
offset = logoBox.localToGlobal(Offset.zero);
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
key: textKey,
'Flutter is awesome',
style: const TextStyle(fontSize: 20),
),
Container(
height: size.height,
width: size.width,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
stops: [0.1, 0.5, 0.7, 0.9],
colors: [
Colors.red,
Colors.orange,
Colors.yellow,
Colors.green
],
),
),
child: const Text(
'Flutter is awesome',
style: TextStyle(fontSize: 20),
),
),
Container(
alignment: Alignment.center,
color: Colors.black,
child: Text(
'Height = ${size.height} && Width = ${size.width} && Position is $offset',
style: const TextStyle(
color: Colors.white,
fontSize: 22,
),
textAlign: TextAlign.center,
),
),
],
),
);
}
}
在上面的代码块中,我们在initState中注册了一个addPostFrameCallback。getSizeAndPosition()使用RenderBox来获取一个部件的大小和位置。代码行RenderBox logoBox = textKey.currentContext!.findRenderObject() as RenderBox;检索与特定部件关联的RenderBox。它使用findRenderObject()方法来找到相关的RenderBox对象。 一旦获得RenderBox,代码会检索其大小并将其赋值给size变量。它还使用localToGlobal(Offset.zero)获取部件相对于屏幕的全局偏移量。最后,调用setState()方法来更新小部件的状态。
页面前后状态对比:
祝编程愉快!