🎯 广告类型
版本 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 参数

参数类型必需默认值说明
posIdString-Banner 广告位 ID
widthint?375宽度(px)
heightint?60高度(px)
mutedIfCanbool?null是否静音播放 (聚合功能)
volumedouble?null音量大小 0.0-1.0 (Android)
bidNotifybool?null是否开启竞价结果回传 (聚合功能)
scenarioIdString?null场景ID (聚合功能)
useSurfaceViewbool?null是否使用SurfaceView 💡 仅Android
enableMixedModebool?null启用Banner混出信息流 (聚合功能)
extraParamsMap<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 参数

参数类型必需说明
posIdStringBanner 广告位 ID
widthdouble宽度,默认 375
heightdouble高度,默认 60
isVisiblebool是否可见,默认 true
onAdLoadedVoidCallback?广告加载成功回调
onAdErrorFunction(String)?广告加载失败回调
onAdClickedVoidCallback?广告点击回调
onAdClosedVoidCallback?广告关闭回调
onRenderSuccessFunction(double, double)?渲染成功回调,返回宽高
onRenderFailFunction(String)?渲染失败回调
onEcpmInfoFunction(Map<String, dynamic>)?ECPM信息回调
onMixedLayoutVoidCallback?混出信息流布局回调

常见尺寸

类型尺寸 (宽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',    // 个人中心
)

用途

  • 便于后台分析不同位置的广告效果
  • 支持按场景配置广告策略
  • 方便问题排查和数据统计

下一步

需要进一步协助?

与 LightCore 技术顾问沟通,获取商业化策略与集成支持。