这并不是一件容易的事情。这里有同样的问题,但没有答案。
在GitHub上有一个活跃的讨论。
对于该问题,有多种解决方案。其中一个方案需要使用rect_getter
包,可以在此处找到这个方案的示例代码。
另外,你也可以看看这个提案。
如果你正在寻找一种简单的方法来解决这个问题,那么目前还没有实现。然而,有一些解决方案,比如我上面提到的方案,还有其他一些包中的解决方案,比如VisibilityDetector
这个类来自于flutter_widgets
包。
有一个包可以实现此目的。
VisibilityDetector小部件包装现有的Flutter小部件,并在小部件的可见性更改时触发回调。
用法:
VisibilityDetector(
key: Key('my-widget-key'),
onVisibilityChanged: (visibilityInfo) {
var visiblePercentage = visibilityInfo.visibleFraction * 100;
debugPrint(
'Widget ${visibilityInfo.key} is ${visiblePercentage}% visible');
},
child: someOtherWidget,
)
我分享一下如何通常检测小部件位置的方法,以提高可见性。
我很好奇如何访问小部件的位置数据,并且还想能够控制ListView子元素的动画状态。
看起来访问小部件的大小和位置的主要方法是通过BuildContext
的context.findRenderObject()
。
然而,这只有在组件构建完成并挂载小部件后才能使用。
通过在一个名为WidgetsBinding.instance.addPostFrameCallback((_) => calculatePosition(context));
的函数中使用context.findRenderObject()
来解决这个问题。
这里有一个包装器组件,您可以在ListView.itemBuilder()
代码中使用。
import 'package:flutter/cupertino.dart';
import 'dart:developer' as developer;
enum POCInViewDirection { up, down, static }
class POCInView extends StatefulWidget {
final Widget child;
final double scrollHeight;
const POCInView({super.key, required this.child, required this.scrollHeight});
@override
POCInState createState() => POCInState();
}
class POCInState extends State<POCInView> {
bool inView = false; // are you in view or not.
double lastPositionY = 0; // used to determine which direction your widget is moving.
POCInViewDirection direction = POCInViewDirection.static; // Set based on direction your moving.
RenderBox? renderBoxRef;
bool skip = true;
@override
void initState() {
super.initState();
developer.log('InitState', name: 'POCInView');
lastPositionY = 0;
renderBoxRef = null;
direction = POCInViewDirection.static;
skip = true;
}
/// Calculate if this widget is in view.
/// uses BuildContext.findRenderObject() to get the RenderBox.
/// RenderBox has localToGlobal which will give you the objects offset(position)
/// Do some math to workout if you object is in view.
/// i.e. take into account widget height and position.
///
/// I only do Y coordinates.
///
void calculatePosition(BuildContext context) {
// findRenderObject() will fail if the widget has been unmounted. so leave if not mounted.
if (!mounted) {
renderBoxRef = null;
return;
}
// It says this can be quite expensive as it will hunt through the view tree to find a RenderBox.
// probably worth timing or seeing if its too much for you view.
// I've put a rough cache in, deleting the ref when its unmounted. mmmmm.
renderBoxRef ??= context.findRenderObject() as RenderBox;
//
inView = false;
if (renderBoxRef is RenderBox) {
Offset childOffset = renderBoxRef!.localToGlobal(Offset.zero);
final double y = childOffset.dy;
final double componentHeight = context.size!.height;
final double screenHeight = widget.scrollHeight;
if (y < screenHeight) {
if (y + componentHeight < -20) {
inView = false;
} else {
inView = true;
}
} else {
inView = false;
}
// work out which direction we're moving. Not quite working right yet.
direction = y > lastPositionY ? POCInViewDirection.down : POCInViewDirection.up;
lastPositionY = y;
//developer.log('In View: $inView, childOffset: ${childOffset.dy.toString()}', name: 'POCInView');
}
skip = false;
}
@override
Widget build(BuildContext context) {
// calculate position after build is complete. this is required to use context.findRenderObject().
WidgetsBinding.instance.addPostFrameCallback((_) => calculatePosition(context));
// fade in when in view.
final oChild = AnimatedOpacity(opacity: inView ? 1 : 0, duration: const Duration(seconds: 1), child: widget.child);
// slide in when in view, and adjust slide direction based on scroll direction.
return AnimatedSlide(
duration: Duration(seconds: inView ? 1 : 0),
offset: Offset(0, inView ? 0.0 : 0.25 * (skip == true ? 0 : (direction == POCInViewDirection.up ? 1 : -1))),
child: oChild,
);
}
}
ListView.builder
,它可以传递一个itemBuilder
,用于构建listView
中一行的“widget”。这样Flutter就能够管理哪些行是可见的并进行渲染,并清除不可见的行以释放内存... - LgFranco