如何知道窗口小部件在视口中是否可见? [英] How to know if a widget is visible within a viewport?

查看:72
本文介绍了如何知道窗口小部件在视口中是否可见?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个视图,该视图的主体由Scaffold和单个ListView组成,列表的每个子代都是一个不同的小部件,代表视图的各个部分"(部分范围从简单的TextViews到排列Column s和Row s),我只想在用户滚动到某些Widgets时显示FloatingActionButon(由于位于列表下方,因此最初不可见).

I have a view that consists of a Scaffold and a single ListView in its body, each children of the list is a different widget that represents various "sections" of the view (sections range from simple TextViews to arrangements of Columns and Rows), I want to show a FloatingActionButon only when the user scrolls over certain Widgets (which aren't initially visible due to being far down the list).

推荐答案

对于已改写的问题,我对您要执行的操作有了更清晰的了解.您有一个小部件列表,并且要根据当前是否在视口中显示这些小部件来决定是否显示浮动操作按钮.

With the rephrased question, I have a clearer understanding about what you're trying to do. You have a list of widgets, and want to decide whether to show a floating action button based on whether those widgets are currently being shown in the viewport.

我写了一个基本的例子来说明这一点.我将在下面描述各种元素,但请注意:

I've written a basic example which shows this in action. I'll describe the various elements below, but please be aware that:

  1. 它使用的GlobalKey往往不太有效
  2. 它连续运行,并在滚动过程中每帧进行一些非最佳计算.

因此,这可能会导致您的应用程序运行缓慢.我将它留给其他人来优化或编写一个更好的答案,该答案使用对渲染树的更好了解来做同样的事情.

Therefore, it might cause your app to slow down. I'll leave it to someone else to optimize or write a better answer that uses a better knowledge of the render tree to do this same thing.

无论如何,这是代码.首先,我将为您提供一种比较幼稚的方式-直接在变量上使用setState,因为它更简单:

Anyways, here's the code. I'll first give you the relatively more naive way of doing it - using setState on a variable directly, as it's simpler:

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),
    );
  }
}

有一些容易解决的问题-它不会隐藏按钮,而是使其透明,每次渲染整个窗口小部件,并且每帧对窗口小部件的位置进行计算.

There's a few easily fixable issues with this - it doesn't hide the button but just makes it transparent, it renders the entire widget each time, and it does the calculations for the position of the widget each frame.

这是一个更优化的版本,在不需要时不进行计算.如果您的列表不断变化,则可能需要向其添加更多逻辑(或者您可以每次都进行计算,并且如果性能足够好,则不必担心).注意它如何使用animationController和AnimatedBuilder来确保每次仅构建相关部分.您还可以通过简单地直接设置animationController的value并自己进行不透明度计算来摆脱淡入/淡出(即,您可能希望它在开始滚动到视图时变得不透明,这必须考虑在内)您物体的高度):

This is a more optimized version, where it doesn't do the calculations if it doesn't need to. You might need to add more logic to it if your list ever changes (or you could just do the calculations each time and if the performance is good enough not worry about it). Note how it uses an animationController and AnimatedBuilder to make sure that only the relevant part builds each time. You could also get rid of the fading in/fading out by simply setting the animationController's value directly and doing the opacity calculation yourself (i.e. you may want it to get opaque as it starts scrolling into view, which would have to take into account the height of your object):

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),
    );
  }
}

这篇关于如何知道窗口小部件在视口中是否可见?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆