【问题标题】:How to know if a flutter image has been seen by the user如何知道用户是否看到了颤振图像
【发布时间】:2019-03-26 13:17:37
【问题描述】:

我在可滚动屏幕中有一个Image 组件。刚开屏时看不到图片,需要向下滚动才能看到。

如何确保图像在用户滚动到该图像后完全被用户看到?我想统计用户的形象印象。

你是如何在 Flutter 中实现这一点的?

【问题讨论】:

  • 您使用的是什么视图,例如 ListView 或 SingleChildScrollView?

标签: dart flutter


【解决方案1】:

我没有太多关于您的代码的信息,所以这就是我解决它的方法。仅当图像在屏幕上完全可见时才会计算展示次数,您可以使用 _count = 表达式更改它。我用简单的Container 代替Image

先看看这个截图。


代码

void main() => runApp(MaterialApp(home: HomePage()),);

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  ScrollController _scrollController;
  double _heightListTile = 56, _heightContainer = 200, _oldOffset = 0, _heightBox, _initialAdd;
  int _initialCount, _count, _previousCount = 0, _itemsInList = 4;

  @override
  void initState() {
    super.initState();
    _heightBox = ((_itemsInList) * _heightListTile) + _heightContainer;
    _scrollController = ScrollController();
    _scrollController.addListener(() {
      double offset = _scrollController.offset;
      if (offset >= _oldOffset) {
        _oldOffset = offset;
        _count = _initialCount + (offset + _initialAdd) ~/ _heightBox;
        if (_count != _previousCount) setState(() {});
        _previousCount = _count;
      }
    });

    Timer.run(() {
      bool isIos = Theme.of(context).platform == TargetPlatform.iOS;
      var screenHeight = MediaQuery.of(context).size.height - (isIos ? 100 : 80); // for non notches phone use 76 instead of 100 (it's the height of status and navigation bar)
      _initialCount = screenHeight ~/ _heightBox;
      _initialAdd = screenHeight % _heightBox;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(_count == null ? "Let's count" : "Images shown = ${_count}")),
      body: ListView.builder(
        itemCount: 100,
        controller: _scrollController,
        itemBuilder: (context, index) {
          if (index == 0) return Container();

          if (index != 0 && index % (_itemsInList + 1) == 0) {
            return Container(
              height: _heightContainer,
              alignment: Alignment.center,
              color: Colors.blue[(index * 20) % 1000],
              child: Text("Image #${(index + 1) ~/ 5}"),
            );
          }

          return SizedBox(height: _heightListTile, child: ListTile(title: Text("Item ${index}")));
        },
      ),
    );
  }
}

【讨论】:

    【解决方案2】:

    此解决方案将检测您的Image 是否已在您的用户屏幕上完全可见,如果是,将更改AppBar 标题。假设您要显示包含一些内容和 Image 的单个页面:

      class ImageDisplayDetection extends StatefulWidget {
      ImageDisplayDetection({Key key,}) : super(key: key);
    
      @override
      _ImageDisplayDetectionState createState() => _ImageDisplayDetectionState();
      }
    
      class  _ImageDisplayDetectionState extends State<ImageDisplayDetection> {
    
      ScrollController _controller; // To get the current scroll offset
    
      var _itemSize = 400.0 ; // The height of your image
    
      double _listSize = 2000.0 ;
    
      double position = 1500.0 ; // position from the top of the list where the image begins
    
      var seen = false ; // to report the visibility of your image
    
    
     @override
     void initState() {
        _controller = ScrollController();
        _controller.addListener(_scrollListener); // The listener will be used to check if the image has become visible
        super.initState();
     }
    
     _scrollListener() {
        setState(() {
          // This 60.0 is the assumed hieght of the bottom navigation buttons so the image won't be considered visible unless it is above these buttons
          if((_controller.offset + MediaQuery.of(context).size.height) >= position + _itemSize + 60.0){
            seen = true ;
          }
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
        backgroundColor: Colors.grey.shade200 ,
        appBar: new AppBar(title: new Text(seen ? 'Image Displayed Successfully' : 'Image not displayed'),),
        body: ListView.builder(
          controller: _controller ,
          itemCount: 1,
          itemBuilder: (context, index) {
            return Container(
                  height: _listSize ,
                  child: new Stack(
                    children: <Widget>[
                      // You can include other childern here such as TextArea
    
                      Positioned(
                        top: position,
                        child: SizedBox(
                          height: _itemSize,
                          width: _itemSize,
                          child: ClipRRect(
                            borderRadius: BorderRadius.circular(5.0),
                            child: Image.asset('assets/images/2.jpg'), //Change the image widget to match your own image source and name
                          ),
                        ),
                      ),
                    ],
                  ),
                );
             }),
           );
         }
       }
    

    如果您想要一个带有多个ListTileslistview,您可以选择这个answer,它可以检测任意索引是否可见并显示在屏幕中某个位置的child

    【讨论】:

      【解决方案3】:

      目前还没有办法知道 listView 中的可见项。关注此issue。您可以在列表视图中添加项目并使用ScrollController 检查您是否已到达列表底部。

      import 'package:flutter/material.dart';
      
      void main() => runApp(MainPage());
      
      class MainPage extends StatefulWidget {
        @override
        _MainPageState createState() => _MainPageState();
      }
      
      class _MainPageState extends State<MainPage> {
        ScrollController _controller;
      
        @override
        void initState() {
          _controller = ScrollController();
          _controller.addListener(_scrollListener);
          super.initState();
        }
      
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
            debugShowCheckedModeBanner: false,
            home: Scaffold(
              backgroundColor: Colors.white,
              body: ListView(
                controller: _controller,
                children: <Widget>[
                  Text(text),
                  Text(text),
                  Text(text),
                  Text(text),
                  Text(text),
                  Image.network(
                      'https://sample-videos.com/img/Sample-png-image-200kb.png'),
                  Text(text),
                ],
              ),
            ),
          );
        }
      
        _scrollListener() {
          if (_controller.offset >= _controller.position.maxScrollExtent &&
              !_controller.position.outOfRange) {
            // reached at the bottom of list
            // Increment the view by one
          }
        }
      
        String text =
            'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur id ornare orci. In aliquet sed leo vel suscipit. Suspendisse eget dolor arcu. Duis fermentum quam suscipit nisl interdum fermentum. Aliquam laoreet, mi eu gravida rutrum, elit ex ornare erat, in egestas leo augue ac nisl. Sed vitae commodo metus, nec vulputate dui. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Phasellus rhoncus tellus nec diam elementum laoreet. Phasellus ac sapien leo. Donec dolor ante, porta quis pellentesque quis, iaculis vitae quam. Sed bibendum tortor a vestibulum malesuada. Duis non nisl congue, fringilla nulla et, laoreet odio.';
      }
      

      【讨论】:

      • 问题是我使用NestedScrollViewSliverAppBar,滚动监听器告诉我我已经到达底部屏幕但这是错误的,因为我还有更多项目要滚动(还没有到达底部)
      【解决方案4】:

      https://pub.dev/packages/visibility_detector 可以用于此。它有一个 VisibilityDetector 小部件,可以包装任何其他小部件并在小部件的可见区域发生更改时通知。通过更多的逻辑,我们可以创建一个 ImpressionDetector,当一个小部件在屏幕上出现超过 X 个时间单位时发出通知:

      /// Widget that fires a callback when its [child] is visible on screen continuously
      /// for more than [durationForImpression]. We consider that the [child] is visible
      /// if its visible fraction is more than [minVisibilityThreshold].
      ///
      /// This widget is built from [VisibilityDetector]. Thus, [childKey] is a key that
      /// should be unique between all [ImpressionDetector] (similarly to VisibilityDetector).
      ///
      /// Callback [onImpression] is only fired once per widget. Notably, if the widget
      /// goes off screen and later comes back, it may be reconstructed as a new widget
      /// in which case there could be a new callback fired.
      class ImpressionDetector extends StatefulWidget {
        final Key childKey;
        final Widget child;
        final VoidCallback onImpression;
        final Duration durationForImpression;
        final double minVisibilityThreshold;
      
        ImpressionDetector({
          @required this.childKey,
          @required this.child,
          @required this.onImpression,
          this.durationForImpression = const Duration(seconds: 2),
          this.minVisibilityThreshold = 0.75,
        });
      
        @override
        State<StatefulWidget> createState() => _ImpressionDetectorState();
      }
      
      class _ImpressionDetectorState extends State<ImpressionDetector> {
        bool beingViewed = false;
        int beingViewedCount = 0;
        bool callbackCalled = false;
      
        @override
        Widget build(BuildContext context) {
          if (callbackCalled) {
            return widget.child;
          }
      
          return VisibilityDetector(
            key: widget.childKey,
            onVisibilityChanged: (info) {
              if (!beingViewed) {
                if (info.visibleFraction > widget.minVisibilityThreshold) {
                  startBeingViewed();
                }
              } else {
                if (info.visibleFraction < widget.minVisibilityThreshold) {
                  stopBeingViewed();
                }
              }
            },
            child: widget.child,
          );
        }
      
        void startBeingViewed() {
          beingViewed = true;
          int currentBeingViewed = ++beingViewedCount;
      
          Future.delayed(widget.durationForImpression, () {
            if (currentBeingViewed == beingViewedCount) {
              widget.onImpression();
              setState(() {
                callbackCalled = true;
              });
            }
          });
        }
      
        void stopBeingViewed() {
          beingViewed = false;
          ++beingViewedCount;
        }
      }
      

      【讨论】:

        猜你喜欢
        • 2021-05-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-28
        • 1970-01-01
        相关资源
        最近更新 更多