🎯 广告类型
版本 GroMore
🏷️ 横幅广告 (Banner)
横幅广告支持两种使用方式:方法调用和 Widget 组件。
Widget 组件方式(推荐)
// 在Widget中直接使用Banner广告组件
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('首页')),
body: Column(
children: [
Expanded(
child: Center(
child: Text('主要内容区域'),
),
),
// Banner广告区域
Container(
width: double.infinity,
height: 60,
color: Colors.grey[100],
child: AdBannerWidget(
posId: 'your_banner_ad_id', // 替换为您的Banner广告位ID
width: 375,
height: 60,
onAdLoaded: () {
print('Banner广告加载成功');
},
onAdError: (error) {
print('Banner广告加载失败: $error');
},
onAdClicked: () {
print('Banner广告被点击');
},
onAdClosed: () {
print('Banner广告被关闭');
},
),
),
],
),
);
}
}
方法调用方式
基础用法
class BannerAdHelper {
static bool _isBannerLoaded = false;
// 基础加载Banner广告
static Future<void> loadBasicBannerAd() async {
try {
final bool result = await GromoreAds.loadBannerAd(
'your_banner_ad_id', // 替换为您的Banner广告位ID
width: 375, // 宽度(px),可选,默认375
height: 60, // 高度(px),可选,默认60
);
_isBannerLoaded = result;
print('Banner广告加载${result ? '成功' : '失败'}');
if (result) {
// 可选:手动显示Banner(通常Banner加载后会自动显示)
await GromoreAds.showBannerAd();
}
} catch (e) {
print('Banner广告加载异常: $e');
}
}
// 高级配置加载Banner广告
static Future<void> loadAdvancedBannerAd() async {
try {
final bool result = await GromoreAds.loadBannerAd(
'your_banner_ad_id',
width: 320,
height: 100,
// 聚合功能参数
mutedIfCan: true, // 静音播放
bidNotify: true, // 竞价结果通知
scenarioId: 'home_page', // 场景ID
useSurfaceView: false, // 使用SurfaceView(Android)
enableMixedMode: true, // 启用Banner混出信息流
extraParams: { // 扩展参数
'customKey1': 'customValue1',
'customKey2': 123,
},
);
_isBannerLoaded = result;
print('高级Banner广告加载${result ? '成功' : '失败'}');
} catch (e) {
print('Banner广告加载异常: $e');
}
}
// 销毁Banner广告
static Future<void> destroyBannerAd() async {
if (!_isBannerLoaded) return;
try {
await GromoreAds.destroyBannerAd();
_isBannerLoaded = false;
print('Banner广告已销毁');
} catch (e) {
print('Banner广告销毁异常: $e');
}
}
}
响应式 Banner
class ResponsiveBanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// 根据屏幕宽度调整Banner尺寸
double bannerWidth = constraints.maxWidth;
double bannerHeight = 60;
// 限制最大宽度
if (bannerWidth > 375) {
bannerWidth = 375;
}
return Center(
child: AdBannerWidget(
posId: 'your_banner_ad_id',
width: bannerWidth,
height: bannerHeight,
onAdLoaded: () => print('响应式Banner加载成功'),
onAdError: (error) => print('响应式Banner加载失败: $error'),
),
);
},
);
}
}
API 方法参数
loadBannerAd 参数
参数 | 类型 | 必需 | 默认值 | 说明 |
---|---|---|---|---|
posId | String | ✅ | - | Banner 广告位 ID |
width | int? | ❌ | 375 | 宽度(px) |
height | int? | ❌ | 60 | 高度(px) |
mutedIfCan | bool? | ❌ | null | 是否静音播放 (聚合功能) |
volume | double? | ❌ | null | 音量大小 0.0-1.0 (Android) |
bidNotify | bool? | ❌ | null | 是否开启竞价结果回传 (聚合功能) |
scenarioId | String? | ❌ | null | 场景ID (聚合功能) |
useSurfaceView | bool? | ❌ | null | 是否使用SurfaceView 💡 仅Android |
enableMixedMode | bool? | ❌ | null | 启用Banner混出信息流 (聚合功能) |
extraParams | Map<String, dynamic>? | ❌ | null | 扩展参数 |
⚠️ 平台差异说明
完全支持的参数:
- Android: 所有参数
- iOS:
posId
,width
,height
,mutedIfCan
,bidNotify
,scenarioId
,enableMixedMode
,extraParams
平台特有参数:
- 💡
volume
: 仅Android支持(iOS SDK无对应API)- 💡
useSurfaceView
: 仅Android支持(iOS使用UIView渲染机制)混出信息流功能:
- Android: 通过
enableMixedMode
参数启用,支持自定义渲染- iOS: 通过
enableMixedMode
参数启用,通过delegate回调nativeExpressBannerAdNeedLayoutUI
处理UI布局参数生效机制:
- iOS端通过
mediation.addParam()
方法配置聚合参数- 部分参数可能需要穿山甲平台后台配置支持才能生效
- 建议在测试环境验证参数效果后再用于生产环境
AdBannerWidget 参数
参数 | 类型 | 必需 | 说明 |
---|---|---|---|
posId | String | ✅ | Banner 广告位 ID |
width | double | ❌ | 宽度,默认 375 |
height | double | ❌ | 高度,默认 60 |
isVisible | bool | ❌ | 是否可见,默认 true |
onAdLoaded | VoidCallback? | ❌ | 广告加载成功回调 |
onAdError | Function(String)? | ❌ | 广告加载失败回调 |
onAdClicked | VoidCallback? | ❌ | 广告点击回调 |
onAdClosed | VoidCallback? | ❌ | 广告关闭回调 |
onRenderSuccess | Function(double, double)? | ❌ | 渲染成功回调,返回宽高 |
onRenderFail | Function(String)? | ❌ | 渲染失败回调 |
onEcpmInfo | Function(Map<String, dynamic>)? | ❌ | ECPM信息回调 |
onMixedLayout | VoidCallback? | ❌ | 混出信息流布局回调 |
常见尺寸
类型 | 尺寸 (宽x高) | 适用场景 |
---|---|---|
标准横幅 | 320x50 | 移动端通用 |
大横幅 | 320x100 | 更显眼的横幅 |
智能横幅 | 屏幕宽x60 | 自适应屏幕宽度 |
矩形横幅 | 300x250 | 内容中间插入 |
高级功能示例
带完整回调的 Widget
AdBannerWidget(
posId: 'your_banner_ad_id',
width: 320,
height: 100,
onAdLoaded: () {
print('Banner广告加载成功');
},
onAdError: (error) {
print('Banner广告加载失败: $error');
},
onRenderSuccess: (width, height) {
print('Banner广告渲染成功: ${width}x$height');
},
onRenderFail: (error) {
print('Banner广告渲染失败: $error');
},
onEcpmInfo: (ecpmData) {
print('Banner ECPM信息: $ecpmData');
final ecpm = ecpmData['ecpm'] ?? '0';
final platform = ecpmData['platform'] ?? '';
print('价格: $ecpm分, 平台: $platform');
},
onMixedLayout: () {
print('Banner混出信息流布局触发');
},
onAdClicked: () {
print('Banner广告被点击');
},
onAdClosed: () {
print('Banner广告被关闭');
},
)
Banner混出信息流功能
// 启用混出模式的API调用方式
await GromoreAds.loadBannerAd(
'your_banner_ad_id',
width: 375,
height: 60,
enableMixedMode: true, // 启用混出功能
bidNotify: true, // 启用竞价通知
scenarioId: 'home_bottom', // 设置场景ID
);
实际场景示例
场景1:固定位置Banner(页面底部)
class HomePageWithBottomBanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('首页')),
body: Column(
children: [
// 主内容区域
Expanded(
child: ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return ListTile(
title: Text('列表项 $index'),
);
},
),
),
// 底部固定Banner广告
SafeArea(
child: AdBannerWidget(
posId: 'your_banner_ad_id',
width: MediaQuery.of(context).size.width,
height: 60,
mutedIfCan: true, // 静音播放,避免干扰用户
bidNotify: true, // 启用竞价通知
scenarioId: 'home_bottom', // 场景标识
onAdLoaded: () => print('底部Banner加载成功'),
onAdError: (error) => print('底部Banner加载失败: $error'),
),
),
],
),
);
}
}
场景2:列表中嵌入Banner
class NewsListWithBanner extends StatelessWidget {
final List<String> newsItems = List.generate(20, (i) => '新闻 ${i + 1}');
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: newsItems.length,
itemBuilder: (context, index) {
// 每5条新闻后插入一个Banner
if (index > 0 && index % 5 == 0) {
return Container(
margin: EdgeInsets.symmetric(vertical: 8),
child: AdBannerWidget(
posId: 'your_banner_ad_id',
width: MediaQuery.of(context).size.width - 32,
height: 60,
enableMixedMode: true, // 启用混出模式,提高填充率
mutedIfCan: true,
scenarioId: 'news_feed',
onAdLoaded: () => print('列表Banner-$index加载成功'),
onEcpmInfo: (data) {
print('Banner-$index ECPM: ${data['ecpm']}');
},
),
);
}
// 普通新闻列表项
return ListTile(
leading: Icon(Icons.article),
title: Text(newsItems[index]),
);
},
);
}
}
场景3:不同场景的Banner尺寸选择
class BannerSizeExamples extends StatelessWidget {
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标准横幅 (320x50) - 适合移动端通用场景
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('标准横幅 (320x50)', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Center(
child: AdBannerWidget(
posId: 'your_banner_ad_id',
width: 320,
height: 50,
scenarioId: 'standard_banner',
),
),
],
),
),
Divider(),
// 大横幅 (320x100) - 更显眼
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('大横幅 (320x100)', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Center(
child: AdBannerWidget(
posId: 'your_banner_ad_id',
width: 320,
height: 100,
scenarioId: 'large_banner',
),
),
],
),
),
Divider(),
// 智能横幅 - 自适应屏幕宽度
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('智能横幅 (全屏宽x60)', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 8),
AdBannerWidget(
posId: 'your_banner_ad_id',
width: screenWidth,
height: 60,
scenarioId: 'smart_banner',
),
],
),
),
Divider(),
// 矩形横幅 (300x250) - 适合内容中间插入
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('矩形横幅 (300x250)', style: TextStyle(fontWeight: FontWeight.bold)),
Text('适合文章详情页中间插入', style: TextStyle(fontSize: 12, color: Colors.grey)),
SizedBox(height: 8),
Center(
child: AdBannerWidget(
posId: 'your_banner_ad_id',
width: 300,
height: 250,
scenarioId: 'medium_rectangle',
),
),
],
),
),
],
),
);
}
}
最佳实践
1. 位置选择
建议位置:
- ✅ 页面底部固定位置(用户滚动时始终可见)
- ✅ 内容自然分割处(如文章段落之间、列表项之间)
- ✅ 导航栏下方(不影响主要交互)
- ❌ 避免遮挡重要按钮或交互区域
示例:
// 好的做法:底部固定
Column(
children: [
Expanded(child: MainContent()),
SafeArea(child: AdBannerWidget(...)),
],
)
// 不好的做法:遮挡提交按钮
Stack(
children: [
SubmitButton(),
Positioned(bottom: 0, child: AdBannerWidget(...)), // ❌ 可能遮挡按钮
],
)
2. 用户体验优化
静音控制:
AdBannerWidget(
posId: 'your_banner_ad_id',
mutedIfCan: true, // ✅ 建议开启静音,避免突然的声音干扰
)
加载状态处理:
AdBannerWidget(
posId: 'your_banner_ad_id',
onAdLoaded: () {
// ✅ 广告加载成功,可以统计或调整UI
print('Banner加载成功');
},
onAdError: (error) {
// ✅ 处理加载失败,避免空白区域
print('Banner加载失败: $error');
// 可以显示备用内容或隐藏广告区域
},
)
3. 生命周期管理
Widget方式:无需手动管理,Widget销毁时会自动清理
方法调用方式:需要手动销毁
class MyPageState extends State<MyPage> {
@override
void dispose() {
// ✅ 页面销毁时清理广告
GromoreAds.destroyBannerAd();
super.dispose();
}
}
4. 响应式设计
自适应宽度:
AdBannerWidget(
posId: 'your_banner_ad_id',
width: MediaQuery.of(context).size.width, // ✅ 适应屏幕宽度
height: 60,
)
不同设备尺寸:
// 根据屏幕宽度选择合适的Banner尺寸
double getBannerWidth(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
if (screenWidth > 600) return 468; // 平板
return screenWidth; // 手机
}
AdBannerWidget(
posId: 'your_banner_ad_id',
width: getBannerWidth(context),
height: 60,
)
5. 提高广告收益
启用混出功能:
AdBannerWidget(
posId: 'your_banner_ad_id',
enableMixedMode: true, // ✅ 提高广告填充率
bidNotify: true, // ✅ 启用竞价通知,优化eCPM
scenarioId: 'home_bottom', // ✅ 标识场景,便于后台分析
)
ECPM监控:
AdBannerWidget(
posId: 'your_banner_ad_id',
onEcpmInfo: (ecpmData) {
final ecpm = ecpmData['ecpm'] ?? '0';
final platform = ecpmData['platform'] ?? '';
// ✅ 记录ECPM数据,用于收益分析
print('广告收益: ¥${int.parse(ecpm) / 100}, 平台: $platform');
// 可以上报到分析系统
Analytics.logEvent('ad_ecpm', {
'ecpm': ecpm,
'platform': platform,
'position': 'home_bottom',
});
},
)
6. 性能优化
避免频繁创建销毁:
// ❌ 不好的做法:频繁重建Widget
setState(() {
// 每次setState都会重建AdBannerWidget
});
// ✅ 好的做法:使用const或缓存Widget
class MyPage extends StatelessWidget {
static final _bannerWidget = AdBannerWidget(
posId: 'your_banner_ad_id',
width: 375,
height: 60,
);
@override
Widget build(BuildContext context) {
return Column(
children: [
MainContent(),
_bannerWidget, // ✅ 复用Widget实例
],
);
}
}
列表中的Banner优化:
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
// ✅ 合理控制Banner数量,避免内存占用过高
if (index > 0 && index % 10 == 0) { // 每10项一个Banner
return AdBannerWidget(
key: ValueKey('banner_$index'), // ✅ 使用key避免重复创建
posId: 'your_banner_ad_id',
width: MediaQuery.of(context).size.width - 32,
height: 60,
);
}
return ListItem(items[index]);
},
)
7. 场景标识使用
场景ID命名规范:
// ✅ 清晰的场景标识
AdBannerWidget(
posId: 'your_banner_ad_id',
scenarioId: 'home_bottom', // 首页底部
// scenarioId: 'news_feed', // 新闻列表
// scenarioId: 'article_detail', // 文章详情
// scenarioId: 'user_center', // 个人中心
)
用途:
- 便于后台分析不同位置的广告效果
- 支持按场景配置广告策略
- 方便问题排查和数据统计