获取widget坐标和大小
可以通过给widget指定GlobalKey 对象,之后再通过GlobalKey 对象获取所关联widget的坐标和大小的信息。
如下代码。这里需要在WidgetsBinding.instance.addPostFrameCallback的回调获取renderBox 的原因是,只有widget 绘制完成后,才能确定其最终的位置和大小,即此时获取到的RenderBox 不为空。
也可以选择在widget点击事件后获取,因为此时widget 必定绘制完成。
class _GetCoordinateExample extends StatefulWidget {
const _GetCoordinateExample({Key? key}) : super(key: key);
@override
State<_GetCoordinateExample> createState() => _GetCoordinateExampleState();
}
class _GetCoordinateExampleState extends State<_GetCoordinateExample> {
final GlobalKey titleKey = GlobalKey();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
final titleRenderBox =
titleKey.currentContext!.findRenderObject() as RenderBox;
final globalCoordinates = titleRenderBox.localToGlobal(Offset.zero);
debugPrint(
'titleRenderBox 全局坐标: $globalCoordinates');
debugPrint( 'titleRenderBox 大小: ${titleRenderBox.size}');
});
});
}
@override
Widget build(BuildContext context) {
return Text(
'Title',
key: titleKey,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
);
}
}
获取坐标的常见问题
获取坐标常会出现获取的坐标不正确,且每次获取坐标的数值都不一样的问题。
出现上述问题,很可能是widget的位置并不是恒定不变的。
常见情况如:
- 该
widget进行了动画。 - 该
widget所在的页面进行了路由动画。
该widget进行了动画
那可以根据你是想要获取widget动画执行前的坐标还是执行坐标后,决定获取坐标的方式。
获取widget动画执行前的坐标
可以在widget渲染完成后获取坐标,之后再执行动画。可以参考如下代码。
class _GetCoordinateBeforeAnimatedExample extends StatefulWidget {
const _GetCoordinateBeforeAnimatedExample({Key? key}) : super(key: key);
@override
State<_GetCoordinateBeforeAnimatedExample> createState() =>
_GetCoordinateBeforeAnimatedExampleState();
}
class _GetCoordinateBeforeAnimatedExampleState
extends State<_GetCoordinateBeforeAnimatedExample> {
final GlobalKey titleKey = GlobalKey();
late bool isEndOfFrame = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
isEndOfFrame = true;
final titleRenderBox =
titleKey.currentContext!.findRenderObject() as RenderBox;
debugPrint(
'titleRenderBox coordinate: ${titleRenderBox.localToGlobal(Offset.zero)}');
});
});
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.sizeOf(context);
return SizedBox(
width: size.width,
height: size.height,
child: Column(
children: [_buildAnimatedTitleAsNeed()],
),
);
}
Widget _buildAnimatedTitleAsNeed() {
final Widget child = Text(
'Title',
key: titleKey,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
);
return isEndOfFrame
? AnimatedContainer(
duration: const Duration(milliseconds: 300),
child: Container(
margin: const EdgeInsets.only(top: 30),
child: child,
),
)
: child;
}
}
获取widget动画执行后的坐标
获取widget动画执行后的坐标也是跟之前的方式类似。
class _GetCoordinateAfterAnimatedExample extends StatefulWidget {
const _GetCoordinateAfterAnimatedExample({Key? key}) : super(key: key);
@override
State<_GetCoordinateAfterAnimatedExample> createState() =>
_GetCoordinateAfterAnimatedExampleState();
}
class _GetCoordinateAfterAnimatedExampleState
extends State<_GetCoordinateAfterAnimatedExample>
with SingleTickerProviderStateMixin {
final GlobalKey titleKey = GlobalKey();
late final AnimationController animationController =
AnimationController(vsync: this);
late final Animation<Offset> _offsetAnimation = Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.5, 0.0),
).animate(CurvedAnimation(
parent: animationController,
curve: Curves.elasticIn,
));
@override
void initState() {
super.initState();
animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
final titleRenderBox =
titleKey.currentContext!.findRenderObject() as RenderBox;
debugPrint(
'titleRenderBox coordinate: ${titleRenderBox.localToGlobal(Offset.zero)}');
}
});
}
@override
void dispose() {
animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.sizeOf(context);
return SizedBox(
width: size.width,
height: size.height,
child: Column(
children: [_buildAnimatedTitleAsNeed()],
),
);
}
Widget _buildAnimatedTitleAsNeed() {
return SlideTransition(
position: _offsetAnimation,
child: Container(
margin: const EdgeInsets.only(top: 30),
child: Text(
'Title',
key: titleKey,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
),
),
);
}
}
该widget所在的页面进行了路由动画
@override
Widget build(BuildContext context) {
final route = ModalRoute.of(context);
route?.animation?.addStatusListener((status) {
if (status == AnimationStatus.completed) {
final titleRenderBox =
titleKey.currentContext!.findRenderObject() as RenderBox;
debugPrint(
'titleRenderBox coordinate: ${titleRenderBox.localToGlobal(
Offset.zero)}');
}
});
return _buildBody();
}