Узнайте, какие элементы в ListView видны
Как я могу узнать, какие предметы в настоящее время visible
или же invisible
в ListView
?
Например, у меня есть 100 пунктов в ListView
и когда я прокручиваю до верхней части экрана или списка, я хочу определить, какие элементы появляются или исчезают из области просмотра.
Иллюстрация:
ht tps:https://stackru.com/images/47c2cdc169f7ea9a3e9e8cc6a2cd83997adf3c14.png
4 ответа
Нет простого способа сделать это. Здесь тот же вопрос, однако, у него нет ответа.
Существует активная проблема GitHub по этому поводу.
Есть несколько решений этой проблемы. Этот Gist показывает тот, который требует rect_getter
пакет
В качестве альтернативы, вы можете взглянуть на это предложение.
TL; DR
Это еще не реализовано, если вы ищете простой способ выяснить это. Однако есть решения, подобные тем, которые я упомянул выше.
Для этого есть пакет .
Виджет 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's
context.findRenderObject()
Однако это можно использовать только после сборки компонента и монтирования виджета.
Это решается с помощью context.findRenderObject() в функции, вызываемой с помощьюWidgetsBinding.instance.addPostFrameCallback((_) => calculatePosition(context));
Вот компонент-оболочка, который вы можете использовать в своем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,
);
}
}
Вы также можете использовать inview_notifier_list . По сути, это обычный ListView, который определяет видимую область, и его дочерние элементы получают уведомление, когда они находятся в этой области.