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

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

问题描述

我有一个视图,其中包含一个 Scaffold 和一个 ListView 主体,列表的每个子项都是一个不同的小部件,代表不同的部分"视图(部分范围从简单的 TextViews 到 Columns 和 Rows 的排列),我只想在用户滚动时显示 FloatingActionButon在某些 Widgets 上(由于在列表的下方,它们最初不可见).

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天全站免登陆