Узнайте, какие элементы в 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, который определяет видимую область, и его дочерние элементы получают уведомление, когда они находятся в этой области.

Другие вопросы по тегам