Flutter交互式视图画廊插件interactiveviewer_gallery_plus的使用

Flutter交互式视图画廊插件interactiveviewer_gallery_plus的使用

interactiveviewer_gallery_plus

pub package

中文文档

该插件是从interactiveviewer_gallery插件fork而来,用于在Flutter中展示图片和视频预览画廊。它支持以下功能:

  1. 双指缩放手势
  2. 双击放大
  3. 左右切换
  4. 背景缩放、平移和透明度调整
  5. 视频自动暂停当失去焦点时
  6. 下拉手势支持左右移动
  7. 更流畅的手势缩放体验

预览

使用此插件实现的图片浏览效果如下所示,在QuanDao【犬岛】应用中可以看到实际效果。

YouTube链接

犬岛下载链接(App Store)

犬岛下载链接(QQ市场)

设置

由于该库基于InteractiveViewer,因此要求Flutter版本在1.20.0及以上。

dependencies:
  interactiveviewer_gallery: ^最新版本号

如何使用

1. 在你的图片网格项中包裹Hero组件

Hero(
    tag: source.url,
    child: // 网格项组件
)

2. 在网格项的GestureDetector中添加跳转到interactiveviewer_gallery

void _openGallery(DemoSourceEntity source) {
  Navigator.of(context).push(
    HeroDialogRoute<void>(
      // DisplayGesture是调试用的,请在实际使用时删除
      builder: (BuildContext context) => InteractiveviewerGallery<DemoSourceEntity>(
          sources: sourceList,
          initIndex: sourceList.indexOf(source),
          itemBuilder: itemBuilder,
          onPageChanged: (int pageIndex) {
            print("nell-pageIndex:$pageIndex");
          },
      ),
    ),
  );
}

3. 编辑itemBuilder:你可以参考example/lib/main.dart然后自定义

Widget itemBuilder(BuildContext context, int index, bool isFocus) {
  DemoSourceEntity sourceEntity = sourceList[index];
  if (sourceEntity.type == 'video') {
    return DemoVideoItem(
      sourceEntity,
      isFocus: isFocus,
    );
  } else {
    return DemoImageItem(sourceEntity);
  }
}

其他

欢迎评论和提PR。


下面是完整的示例代码:

import 'package:cached_network_image/cached_network_image.dart';
import 'package:example/display_gesture_widget.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:interactiveviewer_gallery_plus/hero_dialog_route.dart';
import 'package:interactiveviewer_gallery_plus/interactiveviewer_gallery_plus.dart';
import 'package:video_player/video_player.dart';

import 'live_photo_player.dart';

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'InteraGallery Demo',
      // DisplayGesture是调试用的,请在实际使用时删除
      home: DisplayGesture(
        child: InteractiveviewDemoPage(),
      ),
    );
  }
}

class DemoSourceEntity {
  int id;
  String url;
  String? previewUrl;
  String type;

  DemoSourceEntity(this.id, this.type, this.url, {this.previewUrl});
}

class InteractiveviewDemoPage extends StatefulWidget {
  static final String sName = "/";

  [@override](/user/override)
  _InteractiveviewDemoPageState createState() => _InteractiveviewDemoPageState();
}

class _InteractiveviewDemoPageState extends State<InteractiveviewDemoPage> {
  List<DemoSourceEntity> sourceList = [
    DemoSourceEntity(0, 'image', 'https://cdn.pixabay.com/photo/2023/04/10/15/56/bowl-7914112_1280.jpg'),
    DemoSourceEntity(1, 'image', 'https://cdn.pixabay.com/photo/2024/09/27/15/20/halloween-9079096_1280.jpg'),
    DemoSourceEntity(2, 'image', 'https://cdn.pixabay.com/photo/2023/08/07/15/18/woman-8175307_1280.jpg'),
    DemoSourceEntity(3, 'image', 'https://cdn.pixabay.com/animation/2023/05/04/16/12/16-12-04-538_512.gif'),
    DemoSourceEntity(4, 'video', 'https://cdn.pixabay.com/video/2023/11/28/191159-889246512_tiny.mp4',
        previewUrl: 'https://cdn.pixabay.com/photo/2024/06/05/19/45/mountains-8811206_1280.jpg'),
    DemoSourceEntity(5, 'live',
        'https://quandaoimages.fixtime.com/d1993230-8584-11ef-b197-f5d39dc8e39e.jpg'),
    DemoSourceEntity(6, 'video',
        'https://cdn.pixabay.com/video/2024/08/20/227567_tiny.mp4',
        previewUrl: 'https://cdn.pixabay.com/photo/2023/01/15/22/48/river-7721287_1280.jpg'),
  ];

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('InteractiveviewerGallery Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.only(top: 50.0),
        child: Wrap(
          children: sourceList.map((source) => _buildItem(source)).toList(),
        ),
      ),
    );
  }

  Widget _buildItem(DemoSourceEntity source) {
    return Hero(
      tag: source.id,
      placeholderBuilder: (BuildContext context, Size heroSize, Widget child) {
        // 保持构建图像,因为这些图像可以在图像画廊的背景中可见
        return child;
      },
      child: GestureDetector(
        onTap: () => _openGallery(source),
        child: Stack(
          alignment: Alignment.center,
          children: [
            CachedNetworkImage(
              imageUrl: source.type == 'video' ? source.previewUrl! : source.url,
              fit: BoxFit.cover,
              width: 100,
              height: 100,
            ),
            source.type == 'video'
                ? Icon(
                    Icons.play_arrow,
                    color: Colors.white,
                  )
                : SizedBox(),
          ],
        ),
      ),
    );
  }

  void _openGallery(DemoSourceEntity source) {
    Navigator.of(context).push(
      HeroDialogRoute<void>(
        // DisplayGesture是调试用的,请在实际使用时删除
        builder: (BuildContext context) => DisplayGesture(
          child: InteractiveviewerGalleryPlus<DemoSourceEntity>(
            sources: sourceList,
            initIndex: sourceList.indexOf(source),
            itemBuilder: itemBuilder,
            onPageChanged: (int pageIndex) {
              print("nell-pageIndex:$pageIndex");
            },
          ),
        ),
      ),
    );
  }

  Widget itemBuilder(BuildContext context, int index, bool isFocus) {
    DemoSourceEntity sourceEntity = sourceList[index];
    if (sourceEntity.type == 'video') {
      return DemoVideoItem(
        sourceEntity,
        isFocus: isFocus,
      );
    } else if (sourceEntity.type == 'live') {
      return Center(
        child: Hero(
          tag: sourceEntity.id,
          child: LivePhotoWrapper(
            key: ValueKey(sourceEntity.url),
            liveUrl: 'https://quandaoimages.fixtime.com/caf69670-8584-11ef-b197-f5d39dc8e39e.MOV',
            height: MediaQuery.of(context).size.width * 0.85,
            markSize: const Size(57, 24),
            width: MediaQuery.of(context).size.width,
            canVideoAutoPlay: true,
            coverPic: CachedNetworkImage(
              imageUrl: sourceEntity.url,
              fit: BoxFit.contain,
            ),
          ),
        ),
      );
    } {
      return DemoImageItem(sourceEntity);
    }
  }
}

class DemoImageItem extends StatefulWidget {
  final DemoSourceEntity source;

  DemoImageItem(this.source);

  [@override](/user/override)
  _DemoImageItemState createState() => _DemoImageItemState();
}

class _DemoImageItemState extends State<DemoImageItem> {
  [@override](/user/override)
  void initState() {
    super.initState();
    print('initState: ${widget.source.id}');
  }

  [@override](/user/override)
  void dispose() {
    super.dispose();
    print('dispose: ${widget.source.id}');
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return GestureDetector(
      behavior: HitTestBehavior.opaque,
      onTap: () => Navigator.of(context).pop(),
      child: Center(
        child: Hero(
          tag: widget.source.id,
          child: CachedNetworkImage(
            imageUrl: widget.source.url,
            fit: BoxFit.contain,
          ),
        ),
      ),
    );
  }
}

class DemoVideoItem extends StatefulWidget {
  final DemoSourceEntity source;
  final bool? isFocus;

  DemoVideoItem(this.source, {this.isFocus});

  [@override](/user/override)
  _DemoVideoItemState createState() => _DemoVideoItemState();
}

class _DemoVideoItemState extends State<DemoVideoItem> {
  VideoPlayerController? _controller;
  late VoidCallback listener;
  String? localFileName;

  _DemoVideoItemState() {
    listener = () {
      if (!mounted) {
        return;
      }
      setState(() {});
    };
  }

  [@override](/user/override)
  void initState() {
    super.initState();
    print('initState: ${widget.source.id}');
    init();
  }

  init() async {
    _controller = VideoPlayerController.network(widget.source.url);
    // 循环播放
    _controller!.setLooping(true);
    await _controller!.initialize();
    setState(() {});
    _controller!.addListener(listener);
  }

  [@override](/user/override)
  void dispose() {
    super.dispose();
    print('dispose: ${widget.source.id}');
    _controller!.removeListener(listener);
    _controller?.pause();
    _controller?.dispose();
  }

  [@override](/user/override)
  void didUpdateWidget(covariant DemoVideoItem oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (oldWidget.isFocus! && !widget.isFocus!) {
      // 暂停
      _controller?.pause();
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return _controller!.value.isInitialized
        ? Stack(
            alignment: Alignment.center,
            children: [
              GestureDetector(
                onTap: () {
                  setState(() {
                    _controller!.value.isPlaying
                        ? _controller!.pause()
                        : _controller!.play();
                  });
                },
                child: Hero(
                  tag: widget.source.id,
                  child: AspectRatio(
                    aspectRatio: _controller!.value.aspectRatio,
                    child: VideoPlayer(_controller!),
                  ),
                ),
              ),
              _controller!.value.isPlaying == true
                  ? SizedBox()
                  : IgnorePointer(
                      ignoring: true,
                      child: Icon(
                        Icons.play_arrow,
                        size: 100,
                        color: Colors.white,
                      ),
                    ),
            ],
          )
        : Theme(
            data: ThemeData(
                cupertinoOverrideTheme:
                    CupertinoThemeData(brightness: Brightness.dark)),
            child: CupertinoActivityIndicator(radius: 30));
  }
}

更多关于Flutter交互式视图画廊插件interactiveviewer_gallery_plus的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter交互式视图画廊插件interactiveviewer_gallery_plus的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何使用 interactiveviewer_gallery_plus 插件在 Flutter 中创建交互式视图画廊的示例代码。这个插件扩展了 Flutter 的 InteractiveViewer 小部件,提供了一些额外的功能和示例。

首先,确保你已经在 pubspec.yaml 文件中添加了 interactiveviewer_gallery_plus 依赖:

dependencies:
  flutter:
    sdk: flutter
  interactiveviewer_gallery_plus: ^最新版本号  # 请替换为实际最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来是一个示例代码,展示如何使用 interactiveviewer_gallery_plus 插件:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Interactive Viewer Gallery Plus Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Interactive Viewer Gallery Plus Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: InteractiveViewerGalleryPlus(
          // 你可以根据需要配置这些参数
          showGrid: true, // 显示网格
          showZoomControls: true, // 显示缩放控件
          children: [
            // 添加你想要展示的子小部件
            _buildItem(Colors.red, 'Item 1'),
            _buildItem(Colors.green, 'Item 2'),
            _buildItem(Colors.blue, 'Item 3'),
          ],
        ),
      ),
    );
  }

  Widget _buildItem(Color color, String label) {
    return Center(
      child: Container(
        width: 200,
        height: 200,
        color: color,
        child: Center(
          child: Text(
            label,
            style: TextStyle(color: Colors.white, fontSize: 24),
          ),
        ),
      ),
    );
  }
}

在这个示例中:

  1. 我们创建了一个 MyApp 类,它包含应用的根 MaterialApp
  2. MyHomePage 类是我们的主页面,包含一个 Scaffold,其中包含一个 AppBar 和一个填充了 InteractiveViewerGalleryPlusPadding
  3. InteractiveViewerGalleryPlus 小部件接受几个参数,如 showGridshowZoomControls,用于控制是否显示网格和缩放控件。
  4. children 参数接受一个 Widget 列表,这些 Widget 将被展示在交互式视图中。
  5. _buildItem 函数用于生成每个展示项的 Widget,这里我们简单地创建了带有颜色和标签的容器。

运行这个代码,你将看到一个包含三个颜色块的交互式视图画廊,你可以缩放和平移这些块。

请确保你已经正确安装并导入了 interactiveviewer_gallery_plus 插件,并根据需要调整代码以适应你的应用需求。

回到顶部