Flutter视图可见性检测插件visibility_detector的使用

发布于 1周前 作者 caililin 来自 Flutter

Flutter视图可见性检测插件visibility_detector的使用

VisibilityDetector 是一个用于监听Flutter中widget可见性的工具。当被包裹的组件的可见性发生变化时,它会触发回调函数。下面我们将详细介绍其使用方法,并提供一个完整的示例demo。

一、基本概念

VisibilityDetector

  • 功能:包装现有的Flutter widget,在该widget的可见性发生变化时触发回调。
  • 注意点
    • 回调不会立即触发,而是会被延迟和合并,确保每个VisibilityDetectorVisibilityDetectorController.updateInterval期间最多被调用一次(除非通过VisibilityDetectorController.notifyNow()强制触发)。
    • 所有的VisibilityDetector的回调会在帧之间同步地一起触发。

VisibilityInfo

  • 包含了关于可见性的信息,例如visibleFraction(可见比例)等。

VisibilityDetectorController

  • 可以用来控制VisibilityDetector的行为,如设置更新间隔或强制触发回调。

二、使用示例

示例1:简单的可见性变化监听

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

class SimpleVisibilityExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return VisibilityDetector(
      key: Key('my-widget-key'),
      onVisibilityChanged: (visibilityInfo) {
        var visiblePercentage = visibilityInfo.visibleFraction * 100;
        debugPrint(
            'Widget ${visibilityInfo.key} is ${visiblePercentage}% visible');
      },
      child: Container(
        width: 200,
        height: 200,
        color: Colors.blue,
        child: Center(
          child: Text("Watch me disappear"),
        ),
      ),
    );
  }
}

示例2:更复杂的布局中的可见性检测

这里展示了一个更加复杂的例子,它创建了一个带有多个VisibilityDetector的表格结构,可以滚动查看不同区域的可见性变化情况。

// ... 省略部分代码 ...

class VisibilityDetectorDemoPageState extends State<VisibilityDetectorDemoPage> {
  // ... 省略部分代码 ...

  @override
  Widget build(BuildContext context) {
    // ... 省略部分代码 ...
    
    final table = !_tableShown
        ? null
        : ClipRect(
            child: Container(
              width: _useScale ? 400 : null,
              height: _useScale ? 300 : null,
              child: ListView.builder(
                key: mainListKey,
                scrollDirection: _layout.mainAxis,
                itemExtent: (_layout.mainAxis == Axis.vertical
                        ? cellHeight
                        : cellWidth) +
                    2 * externalCellPadding,
                itemBuilder: (BuildContext context, int primaryIndex) {
                  return _useSlivers
                      ? SliverDemoPageSecondaryAxis(
                          key: secondaryScrollableKey(primaryIndex),
                          primaryIndex: primaryIndex,
                          secondaryAxis: _layout.secondaryAxis,
                          reverse: _layout.reverse,
                          useScale: _useScale,
                        )
                      : DemoPageSecondaryAxis(
                          key: secondaryScrollableKey(primaryIndex),
                          primaryIndex: primaryIndex,
                          secondaryAxis: _layout.secondaryAxis,
                          reverse: _layout.reverse,
                          useScale: _useScale,
                        );
                },
              ),
            ),
          );

    return Scaffold(
      appBar: AppBar(title: const Text(title)),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            shape: const Border(),
            onPressed: _toggleLayout,
            heroTag: null,
            child: Text('Layout'),
          ),
          const SizedBox(width: 8),
          FloatingActionButton(
            shape: const Border(),
            onPressed: _toggleSlivers,
            heroTag: null,
            child: _useSlivers
                ? const Text('RenderBox')
                : const Text('RenderSliver'),
          ),
          const SizedBox(width: 8),
          FloatingActionButton(
            shape: const Border(),
            onPressed: _toggleTable,
            heroTag: null,
            child: _tableShown ? const Text('Hide') : const Text('Show'),
          ),
          const SizedBox(width: 8),
          FloatingActionButton(
            key: scaleButtonKey,
            shape: const Border(),
            onPressed: _toggleScale,
            heroTag: null,
            child: _useScale ? const Text('Scale') : const Text('No scaling'),
          ),
        ],
      ),
      body: Column(
        children: <Widget>[
          _tableShown ? Expanded(child: table!) : const Spacer(),
          VisibilityReport(
              title:
                  'Visibility (${_useSlivers ? "RenderSliver" : "RenderBox"})'),
        ],
      ),
    );
  }
}

// ... 省略其他辅助类和方法 ...

三、测试注意事项

对于包含VisibilityDetector的widget进行单元测试时,建议将VisibilityDetectorController.instance.updateInterval设置为Duration.zero,这使得可见性变化能够立即报告,避免因定时器未完成而导致的错误。

testWidgets('Test VisibilityDetector', (WidgetTester tester) async {
  VisibilityDetectorController.instance.updateInterval = Duration.zero;

  await tester.pumpWidget(MyApp());

  // Your test code here...

  VisibilityDetectorController.instance.updateInterval = Duration(seconds: 1);
});

四、已知限制

  • VisibilityDetector仅考虑其边界框,不包括透明度的影响。
  • 报告的visibleFraction可能无法准确反映覆盖在其上的其他widgets对其遮挡的情况。

以上就是对visibility_detector插件的基本介绍及其使用方式。希望这些信息能帮助你更好地理解和运用这个强大的工具!


更多关于Flutter视图可见性检测插件visibility_detector的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter视图可见性检测插件visibility_detector的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何使用Flutter的visibility_detector插件来检测视图可见性的示例代码。visibility_detector插件允许你检测一个widget在屏幕上的可见性状态,这在优化UI性能或处理滚动视图中的元素时非常有用。

首先,确保你的pubspec.yaml文件中包含了visibility_detector依赖:

dependencies:
  flutter:
    sdk: flutter
  visibility_detector: ^0.2.0  # 请检查最新版本号

然后,运行flutter pub get来安装依赖。

下面是一个简单的示例,展示如何使用VisibilityDetector来检测一个widget的可见性:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Visibility Detector Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Expanded(
                child: ListView.builder(
                  itemCount: 20,
                  itemBuilder: (context, index) {
                    return VisibilityDetector(
                      key: ValueKey('item_$index'),
                      onVisibilityChanged: (VisibilityInfo info) {
                        print('Item $index is ${info.visibleFraction == 1.0 ? "fully" : "partially"} visible.');
                      },
                      child: Container(
                        height: 100,
                        color: index % 2 == 0 ? Colors.blue : Colors.red,
                        child: Center(
                          child: Text(
                            'Item $index',
                            style: TextStyle(color: Colors.white),
                          ),
                        ),
                      ),
                    );
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

在这个示例中,我们创建了一个包含20个项目的ListView。每个项目都被包装在一个VisibilityDetector widget中。当项目的可见性发生变化时,onVisibilityChanged回调函数会被触发,并打印出项目的可见性状态(完全可见或部分可见)。

关键点解释:

  1. VisibilityDetector:这是检测widget可见性的核心widget。
  2. key: ValueKey('item_$index'):为每个项目分配一个唯一的key,以确保当列表项重建时,VisibilityDetector能够正确跟踪每个项目的可见性状态。
  3. onVisibilityChanged: (VisibilityInfo info):这是一个回调函数,当widget的可见性发生变化时会被调用。VisibilityInfo对象包含了关于widget可见性的详细信息,例如visibleFraction(可见部分的比例)。

这个示例代码提供了一个基础框架,你可以根据实际需求进一步扩展和定制,例如根据不同的可见性状态执行不同的逻辑。

回到顶部