📼 宫格与双Feed小视频
宫格与双Feed小视频提供轻量化的视频浏览入口,适用于首页导流、内容聚合等场景。插件同时支持 NativeView 组件 与 API 控制 两种接入方式。
平台支持:
- ✅ Android: 完整支持宫格与双Feed布局
- ⚠️ iOS: 暂未实现,显示"当前平台暂不支持宫格小视频"
🚪 接入方式概览
方式 | 推荐场景 | 实现特点 |
---|---|---|
GridVideoNativeView 组件(推荐) | Flutter 页面内嵌宫格内容,追求最快集成 | 自动管理创建/销毁,Flutter 布局友好 |
API 调用 | 需要在原生容器中展示,或兼容历史流程 | 主动控制生命周期 |
两种方式共用
GridVideoConfig
配置对象,事件回调也通过同一套监听体系。
✅ 方式一:使用 GridVideoNativeView
(推荐)
GridVideoNativeView
通过 PlatformView 内嵌原生宫格/双Feed视图,Flutter 端仅需像普通 Widget 一样使用。
基础用法
import 'package:flutter/material.dart';
import 'package:pangrowth_content/pangrowth_content.dart';
class GridVideoPage extends StatelessWidget {
const GridVideoPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('宫格小视频')),
body: GridVideoNativeView(
layout: GridVideoLayout.grid, // grid-宫格布局, doubleFeed-双Feed布局
config: const GridVideoConfig(
scene: 'home_grid', // 场景标识,用于内容定制
cardStyle: GridVideoCardStyle.normal, // normal-普通, staggered-瀑布流
enableRefresh: true, // 支持下拉刷新
),
),
);
}
}
双Feed布局示例
GridVideoNativeView(
layout: GridVideoLayout.doubleFeed, // 使用双Feed布局
config: const GridVideoConfig(
scene: 'feed_channel',
cardStyle: GridVideoCardStyle.staggered, // 瀑布流样式
enableRefresh: true,
),
)
使用控制器进行外部控制
通过 GridVideoController
可以在外部控制宫格视频的刷新、滚动等行为:
class GridVideoWithControllerPage extends StatefulWidget {
const GridVideoWithControllerPage({super.key});
@override
State<GridVideoWithControllerPage> createState() => _GridVideoWithControllerPageState();
}
class _GridVideoWithControllerPageState extends State<GridVideoWithControllerPage> {
late final GridVideoController _controller;
@override
void initState() {
super.initState();
_controller = GridVideoController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('宫格小视频'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => _controller.refresh(),
),
IconButton(
icon: const Icon(Icons.arrow_upward),
onPressed: () => _controller.scrollToTop(),
),
],
),
body: GridVideoNativeView(
layout: GridVideoLayout.grid,
config: const GridVideoConfig(scene: 'home_grid'),
controller: _controller,
),
);
}
}
GridVideoController 方法
方法 | 说明 | 使用场景 |
---|---|---|
refresh() | 刷新宫格/双Feed数据 | 用户下拉刷新或点击刷新按钮 |
scrollToTop() | 滚动到顶部 | Tab切换回来时快速回到顶部 |
backRefresh() | 返回场景时的挽留刷新 | 从详情页返回列表时自动刷新 |
setUserVisible(bool) | 控制可见性 | Tab切换时暂停/恢复播放 |
canBackPress() | 校验返回键是否可直接关闭 | Android返回键拦截 |
dispose() | 销毁视频实例并清理状态 | Widget销毁时调用 |
监听宫格视频事件
可通过 GridVideoListener
获取组件创建、销毁及交互事件,便于埋点或自定义 UI:
GridVideoNativeView(
layout: GridVideoLayout.grid,
config: const GridVideoConfig(scene: 'home_grid'),
listener: GridVideoListener(
onGridReady: (gridId) {
debugPrint('宫格视图就绪: $gridId');
},
onGridDisposed: () {
debugPrint('宫格视图已释放');
},
onGridError: (error) {
debugPrint('宫格视图加载失败: $error');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('宫格加载失败,请稍后重试')),
);
},
onItemClick: (videoId) {
debugPrint('点击视频: $videoId');
},
onItemShow: (videoId) {
debugPrint('展示视频: $videoId');
},
onRefreshComplete: () {
debugPrint('刷新完成');
},
),
)
生命周期与限制
- 单实例约束:插件内部限制同一时刻仅允许存在一个宫格视频实例,创建新的
GridVideoNativeView
前需确保旧实例已被销毁。 - 平台视图限制:PlatformView 由原生渲染,避免在其上方叠加
Positioned
/Stack
等复杂布局,必要时可在外层使用安全区域、背景容器。 - 销毁策略:
GridVideoNativeView
在dispose
阶段会自动调用PangrowthContent.destroyGridVideo
,如果在外层缓存了gridId
,可在自定义流程中重复调用(幂等)。
🧭 方式二:使用 API 控制宫格视频
API 方式用于历史项目兼容或需要原生容器承载的场景。如无特殊需求,仍建议优先选择 NativeView 方案。
调用流程
createGridVideo(config)
或createDoubleFeedVideo(config)
创建宫格/双Feed视频,获得videoId
;showGridVideo(videoId)
打开原生宫格视频页面(Android 启动 Activity,iOS 推入控制器);- 页面结束时调用
destroyGridVideo(videoId)
释放资源。
宫格布局参考实现
class GridVideoLauncher {
String? _videoId;
Future<void> openGridVideo() async {
final result = await PangrowthContent.createGridVideo(
const GridVideoConfig(
scene: 'home_grid',
cardStyle: GridVideoCardStyle.normal,
enableRefresh: true,
),
);
if (result['success'] != true) {
debugPrint('创建宫格视频失败: $result');
return;
}
_videoId = result['videoId'] as String;
// Android: 可指定容器ID
// iOS: 可指定展示样式
await PangrowthContent.showGridVideo(
_videoId!,
containerId: null, // Android专用,默认使用android.R.id.content
presentationStyle: Platform.isIOS ? 'pageSheet' : null, // iOS专用
);
}
Future<void> dispose() async {
if (_videoId != null) {
await PangrowthContent.destroyGridVideo(_videoId!);
_videoId = null;
}
}
}
双Feed布局参考实现
class DoubleFeedVideoLauncher {
String? _videoId;
Future<void> openDoubleFeedVideo() async {
final result = await PangrowthContent.createDoubleFeedVideo(
const GridVideoConfig(
scene: 'feed_channel',
cardStyle: GridVideoCardStyle.staggered, // 瀑布流样式
enableRefresh: true,
),
);
if (result['success'] != true) {
debugPrint('创建双Feed视频失败: $result');
return;
}
_videoId = result['videoId'] as String;
await PangrowthContent.showDoubleFeedVideo(
_videoId!,
containerId: null,
presentationStyle: Platform.isIOS ? 'fullScreen' : null,
);
}
Future<void> dispose() async {
if (_videoId != null) {
await PangrowthContent.destroyDoubleFeedVideo(_videoId!);
_videoId = null;
}
}
}
destroyGridVideo
和destroyDoubleFeedVideo
为幂等调用,即使宫格视频已关闭再次调用也不会抛出异常。
🧩 公共配置项(GridVideoConfig
)
以下参数两种接入方式均可使用,未显式赋值时会使用 SDK 默认值。
通用配置
参数 | 类型 | 说明 | 默认值 |
---|---|---|---|
cardStyle | GridVideoCardStyle? | 卡片样式:<br/>- GridVideoCardStyle.normal : 普通样式<br/>- GridVideoCardStyle.staggered : 瀑布流样式 | null(使用SDK默认) |
scene | String? | 场景标识,用于内容定制业务,建议传入有业务含义的标识如 'home_grid' 、'feed_channel' 等 | null |
enableRefresh | bool? | 是否支持下拉刷新 | null(使用SDK默认) |
reportTopPadding | double? | 举报页面顶部内边距,单位 dp,用于适配自定义标题栏 | null |
extra | Map<String, dynamic>? | 自定义扩展字段,将在事件回调中透传 | null |
📡 事件监听
通过 GridVideoListener
获取创建、销毁、交互回调。
常见宫格视频事件如下:
GridVideoEventType | 触发时机 | 常用字段 |
---|---|---|
gridReady | 宫格视图创建完成 | gridId |
itemClick | 用户点击视频卡片 | videoId (视频ID) |
itemShow | 视频卡片曝光 | videoId |
refresh | 页面下拉刷新完成 | - |
loadMore | 列表加载更多完成 | - |
pageShow | 宫格视图对用户可见 | contentId (gridId) |
pageHide | 宫格视图离开前台 | contentId |
destroyed | 原生页面销毁 | contentId |
💡 使用场景与实践
场景1:首页引导位
适合使用宫格布局(2x2),不自动播放,用户点击后进入沉浸式播放:
GridVideoNativeView(
layout: GridVideoLayout.grid,
config: const GridVideoConfig(
scene: 'home_entry',
cardStyle: GridVideoCardStyle.normal,
enableRefresh: false, // 首页引导位通常不需要刷新
),
)
场景2:频道聚合页
适合使用双Feed或宫格布局,配合下拉刷新和自动播放:
GridVideoNativeView(
layout: GridVideoLayout.doubleFeed,
config: const GridVideoConfig(
scene: 'channel_feed',
cardStyle: GridVideoCardStyle.staggered, // 瀑布流提升视觉效果
enableRefresh: true,
),
)
场景3:TabBar中的宫格视频
配合 GridVideoController
实现Tab切换时的暂停/恢复:
class VideoTabsPage extends StatefulWidget {
const VideoTabsPage({super.key});
@override
State<VideoTabsPage> createState() => _VideoTabsPageState();
}
class _VideoTabsPageState extends State<VideoTabsPage> with SingleTickerProviderStateMixin {
late final TabController _tabController;
late final GridVideoController _gridController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_gridController = GridVideoController();
_tabController.addListener(() {
// Tab切换时控制视频播放状态
if (_tabController.index == 1) {
_gridController.setUserVisible(true); // 宫格Tab被选中,恢复播放
} else {
_gridController.setUserVisible(false); // 切换到其他Tab,暂停播放
}
});
}
@override
void dispose() {
_tabController.dispose();
_gridController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('视频内容'),
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: '推荐'),
Tab(text: '宫格'),
Tab(text: '关注'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
const Center(child: Text('推荐页面')),
GridVideoNativeView(
layout: GridVideoLayout.grid,
config: const GridVideoConfig(scene: 'tab_grid'),
controller: _gridController,
),
const Center(child: Text('关注页面')),
],
),
);
}
}
🔗 相关内容
信息流视频卡片(VideoCard)与单卡片(VideoSingleCard)
注意:VideoCard 和 VideoSingleCard 仅提供 API 方式,没有对应的 NativeView Widget。
如需在信息流中集成视频卡片,请使用以下 API 方法:
信息流视频卡片(VideoCard)
// 创建推荐流视频卡片
final card = await PangrowthContent.createVideoCardForRecommend(
VideoCardConfig(
scene: 'feed_recommend',
// 更多配置参数请查看 VideoCardConfig 定义
),
);
final cardId = card['videoId'] as String;
// 使用完毕后销毁
await PangrowthContent.destroyVideoCard(cardId);
支持的创建方法:
createVideoCardForNews(config)
: 资讯/新闻信息流createVideoCardForRecommend(config)
: 推荐流(默认)createVideoCardForSearch(config)
: 搜索结果
单卡片入口(VideoSingleCard)
// 创建新闻单卡片
final singleCard = await PangrowthContent.createVideoSingleCardForNews(
VideoSingleCardConfig(
scene: 'campaign',
// 更多配置参数请查看 VideoSingleCardConfig 定义
),
);
final singleCardId = singleCard['videoId'] as String;
// 使用完毕后销毁
await PangrowthContent.destroyVideoSingleCard(singleCardId);
由于 VideoCard 和 VideoSingleCard 没有 Widget 封装,需要开发者自行处理原生视图的嵌入和生命周期管理。如果需要在 Flutter 页面中使用,建议优先考虑
GridVideoNativeView
。
🛠️ 故障排查
问题1:宫格视频无法创建或显示空白
可能原因:
- 未完成插件初始化,确保调用了
PangrowthContent.start()
- 穿山甲SDK配置不正确,检查
appId
和appName
是否正确 - iOS平台暂不支持,确认当前运行在 Android 设备上
解决方法:
// 1. 确保在使用宫格视频前完成初始化
await PangrowthContent.start(
startDrama: true, // 或 startStory: true
);
// 2. 检查当前平台
if (!Platform.isAndroid) {
debugPrint('当前平台不支持宫格小视频');
return;
}
// 3. 监听错误事件
GridVideoListener(
onGridError: (error) {
debugPrint('宫格视频错误: $error');
},
)
问题2:切换Tab时宫格视频仍在播放
原因:未调用 setUserVisible
控制播放状态
解决方法:
// Tab切换监听
_tabController.addListener(() {
final isGridTabActive = _tabController.index == 1;
_gridController.setUserVisible(isGridTabActive);
});
问题3:宫格视频内存泄漏
原因:未正确销毁宫格视频实例
解决方法:
@override
void dispose() {
// 方式1: NativeView会自动销毁,但如果使用了Controller需要手动释放
_gridController.dispose();
// 方式2: API方式必须手动销毁
if (_videoId != null) {
PangrowthContent.destroyGridVideo(_videoId!);
}
super.dispose();
}
问题4:宫格视频创建成功但showGridVideo失败
原因:Android端可能是容器ID配置问题,iOS端可能是展示样式不支持
解决方法:
// Android: 使用默认容器ID
await PangrowthContent.showGridVideo(videoId, containerId: null);
// iOS: 使用支持的展示样式
await PangrowthContent.showGridVideo(
videoId,
presentationStyle: 'pageSheet', // 或 'fullScreen', 'formSheet'
);
🌍 平台差异与适配建议
特性 | Android | iOS | 说明 |
---|---|---|---|
宫格布局 | ✅ 完整支持 | ❌ 暂未实现 | iOS平台显示"当前平台暂不支持宫格小视频" |
双Feed布局 | ✅ 完整支持 | ❌ 暂未实现 | 同上 |
NativeView组件 | ✅ 可用 | ⚠️ 显示不支持提示 | iOS端虽然可以创建Widget但会显示提示文本 |
API方式 | ✅ 可用 | ⚠️ SDK未实现 | iOS端调用API会返回错误 |
容器ID(containerId ) | ✅ 支持 | - | 仅Android使用,iOS无此参数 |
展示样式(presentationStyle ) | - | ✅ 支持 | 仅iOS使用,Android无此参数 |
跨平台适配建议
如需在同一代码库中支持Android和iOS,建议:
Widget buildGridVideo() {
if (Platform.isAndroid) {
// Android: 使用GridVideoNativeView
return GridVideoNativeView(
layout: GridVideoLayout.grid,
config: const GridVideoConfig(scene: 'home_grid'),
);
} else {
// iOS: 显示占位内容或其他替代方案
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.video_library, size: 64, color: Colors.grey),
const SizedBox(height: 16),
const Text('iOS平台暂不支持宫格小视频'),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {
// 可以跳转到沉浸式小视频页面作为替代
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const VideoNativeViewPage(),
),
);
},
child: const Text('查看沉浸式小视频'),
),
],
),
);
}
}
📚 参考文档
- 沉浸式小视频 - 全屏沉浸式视频播放
- 个人主页NativeView - 用户个人主页组件
- 事件处理 - 完整的事件监听机制
- Widget组件总览 - 所有组件导航