Flutter交互式视图画廊插件interactiveviewer_gallery_plus的使用
Flutter交互式视图画廊插件interactiveviewer_gallery_plus的使用
interactiveviewer_gallery_plus
该插件是从interactiveviewer_gallery
插件fork而来,用于在Flutter中展示图片和视频预览画廊。它支持以下功能:
- 双指缩放手势
- 双击放大
- 左右切换
- 背景缩放、平移和透明度调整
- 视频自动暂停当失去焦点时
- 下拉手势支持左右移动
- 更流畅的手势缩放体验
预览
使用此插件实现的图片浏览效果如下所示,在QuanDao【犬岛】应用中可以看到实际效果。
设置
由于该库基于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 回复