Как узнать, виджет ли виджет в области просмотра? - PullRequest
0 голосов
/ 27 июня 2018

У меня есть представление, которое состоит из Scaffold и одного ListView в его теле, каждый дочерний элемент списка является отдельным виджетом, который представляет различные «разделы» представления (разделы варьируются от простых TextViews до компоновок Column с и Row с), я хочу показать FloatingActionButon только тогда, когда пользователь прокручивает определенные Widgets (которые изначально не видны из-за того, что находятся далеко внизу списка).

Ответы [ 3 ]

0 голосов
/ 28 июня 2018

С перефразированным вопросом у меня есть более четкое понимание того, что вы пытаетесь сделать. У вас есть список виджетов, и вы хотите решить, отображать ли кнопку с плавающим действием в зависимости от того, отображаются ли эти виджеты в окне просмотра.

Я написал простой пример, который показывает это в действии. Я опишу различные элементы ниже, но учтите, что:

  1. Он использует GlobalKey, который не слишком эффективен
  2. Он работает непрерывно и выполняет неоптимальные вычисления каждый кадр во время прокрутки.

Следовательно, это может привести к замедлению работы вашего приложения. Я оставлю это кому-то другому, чтобы оптимизировать или написать лучший ответ, который использует лучшее знание дерева рендеринга, чтобы сделать то же самое.

В любом случае, вот код. Сначала я дам вам относительно более простой способ сделать это - использовать setState для переменной напрямую, так как это проще:

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new MyAppState();
}

class MyAppState extends State<MyApp> {
  GlobalKey<State> key = new GlobalKey();

  double fabOpacity = 1.0;

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Scrolling."),
        ),
        body: NotificationListener<ScrollNotification>(
          child: new ListView(
            itemExtent: 100.0,
            children: [
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              new MyObservableWidget(key: key),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder()
            ],
          ),
          onNotification: (ScrollNotification scroll) {
            var currentContext = key.currentContext;
            if (currentContext == null) return false;

            var renderObject = currentContext.findRenderObject();
            RenderAbstractViewport viewport = RenderAbstractViewport.of(renderObject);
            var offsetToRevealBottom = viewport.getOffsetToReveal(renderObject, 1.0);
            var offsetToRevealTop = viewport.getOffsetToReveal(renderObject, 0.0);

            if (offsetToRevealBottom.offset > scroll.metrics.pixels ||
                scroll.metrics.pixels > offsetToRevealTop.offset) {
              if (fabOpacity != 0.0) {
                setState(() {
                  fabOpacity = 0.0;
                });
              }
            } else {
              if (fabOpacity == 0.0) {
                setState(() {
                  fabOpacity = 1.0;
                });
              }
            }
            return false;
          },
        ),
        floatingActionButton: new Opacity(
          opacity: fabOpacity,
          child: new FloatingActionButton(
            onPressed: () {
              print("YAY");
            },
          ),
        ),
      ),
    );
  }
}

class MyObservableWidget extends StatefulWidget {
  const MyObservableWidget({Key key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => new MyObservableWidgetState();
}

class MyObservableWidgetState extends State<MyObservableWidget> {
  @override
  Widget build(BuildContext context) {
    return new Container(height: 100.0, color: Colors.green);
  }
}

class ContainerWithBorder extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      decoration: new BoxDecoration(border: new Border.all(), color: Colors.grey),
    );
  }
}

В этом есть несколько легко решаемых проблем - она ​​не скрывает кнопку, а просто делает ее прозрачной, она каждый раз визуализирует весь виджет и выполняет расчеты положения виджета в каждом кадре.

Это более оптимизированная версия, где она не выполняет вычисления, если в этом нет необходимости. Возможно, вам придется добавить больше логики, если ваш список когда-либо изменится (или вы можете просто делать вычисления каждый раз и если производительность достаточно хорошая, не беспокойтесь об этом). Обратите внимание, как он использует animationController и AnimatedBuilder, чтобы убедиться, что каждый раз создается только соответствующая часть. Вы также можете избавиться от затухания / затухания, просто напрямую установив value animationController и выполнив вычисление непрозрачности самостоятельно (т. Е. Вы можете захотеть, чтобы оно стало непрозрачным, когда оно начнет прокручиваться, что необходимо учитывать высота вашего объекта):

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new MyAppState();
}

class MyAppState extends State<MyApp> with TickerProviderStateMixin<MyApp> {
  GlobalKey<State> key = new GlobalKey();

  bool fabShowing = false;

  // non-state-managed variables
  AnimationController _controller;
  RenderObject _prevRenderObject;
  double _offsetToRevealBottom = double.infinity;
  double _offsetToRevealTop = double.negativeInfinity;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(vsync: this, duration: Duration(milliseconds: 300));
    _controller.addStatusListener((val) {
      if (val == AnimationStatus.dismissed) {
        setState(() => fabShowing = false);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Scrolling."),
        ),
        body: NotificationListener<ScrollNotification>(
          child: new ListView(
            itemExtent: 100.0,
            children: [
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              new MyObservableWidget(key: key),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder(),
              ContainerWithBorder()
            ],
          ),
          onNotification: (ScrollNotification scroll) {
            var currentContext = key.currentContext;
            if (currentContext == null) return false;

            var renderObject = currentContext.findRenderObject();

            if (renderObject != _prevRenderObject) {
              RenderAbstractViewport viewport = RenderAbstractViewport.of(renderObject);
              _offsetToRevealBottom = viewport.getOffsetToReveal(renderObject, 1.0).offset;
              _offsetToRevealTop = viewport.getOffsetToReveal(renderObject, 0.0).offset;
            }

            final offset = scroll.metrics.pixels;

            if (_offsetToRevealBottom < offset && offset < _offsetToRevealTop) {
              if (!fabShowing) setState(() => fabShowing = true);

              if (_controller.status != AnimationStatus.forward) {
                _controller.forward();
              }
            } else {
              if (_controller.status != AnimationStatus.reverse) {
                _controller.reverse();
              }
            }
            return false;
          },
        ),
        floatingActionButton: fabShowing
            ? new AnimatedBuilder(
                child: new FloatingActionButton(
                  onPressed: () {
                    print("YAY");
                  },
                ),
                builder: (BuildContext context, Widget child) => Opacity(opacity: _controller.value, child: child),
                animation: this._controller,
              )
            : null,
      ),
    );
  }
}

class MyObservableWidget extends StatefulWidget {
  const MyObservableWidget({Key key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => new MyObservableWidgetState();
}

class MyObservableWidgetState extends State<MyObservableWidget> {
  @override
  Widget build(BuildContext context) {
    return new Container(height: 100.0, color: Colors.green);
  }
}

class ContainerWithBorder extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      decoration: new BoxDecoration(border: new Border.all(), color: Colors.grey),
    );
  }
}
0 голосов
/ 29 июля 2019

https://pub.dev/packages/flutter_widgets обеспечивает эту функцию своим виджетом VisibilityDetector, который может обернуть любой другой Widget и уведомлять об изменении видимой области виджета:

VisibilityDetector(
   key: Key("unique key"),
   onVisibilityChanged: (VisibilityInfo info) {
       debugPrint("${info.visibleFraction} of my widget is visible");
   },
   child: MyWidgetToTrack());
)
0 голосов
/ 27 июня 2018

Трудно сказать, не видя некоторый код; особенно логика, лежащая в основе того, почему определенный элемент виден или нет. Я могу дать вам несколько общих указаний.

Во-первых, во Флаттере состояние обычно держится на более высоком уровне, а затем под ним строятся вещи ниже этого уровня. В этом случае я бы ожидал, что виджет, который включает ваш ListView, будет иметь «состояние», которое описывает, будет ли элемент видимым или нет; в этом случае ваш FloatingActionButton может быть просто показан или не основан на этом решении.

Возможно, вы можете переместить логику, чтобы получить данные, используемые для создания вашего ListView, в виджет, содержащий ListView, или, возможно, в другой класс, совместно используемый этими двумя. Затем вы можете передать данные либо напрямую (если это только один или два слоя виджетов), либо использовать InheritedWidget, если имеется несколько слоев виджетов. ( Этот веб-сайт содержит хороший пример использования InheritedWidgets с источником здесь ).

Во-вторых, можно использовать GlobalKey для доступа к состоянию виджета ниже вашего в стеке. Однако, как правило, это не надежный способ получения информации, так как он не приведет к перестроению при изменении состояния дочернего виджета.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...