向 Flutter 应用添加 AdMob 横幅内嵌广告和原生内嵌广告

在此 Codelab 中,您将在 Flutter 应用中植入 AdMob 横幅广告和 AdMob 原生内嵌广告。

构建内容

此 Codelab 将指导您使用 Google 移动广告 Flutter 插件在 Flutter 应用中植入 AdMob 横幅内嵌广告和 AdMob 原生内嵌广告。

如果在此 Codelab 的操作期间遇到任何问题(代码错误、语法错误、措辞含义不明等),可以通过 Codelab 左下角的报告错误链接报告相应问题。

学习内容

  • 如何配置 Google 移动广告 Flutter 插件
  • 如何在 Flutter 应用中实现内嵌横幅激励广告

所需条件

  • Android Studio 4.1 或更高版本
  • Xcode 12 或更高版本(用于 iOS 开发)

您如何评价自己在 AdMob 方面的经验水平?

新手水平 中等水平 熟练水平

您如何评价自己在 Flutter 方面的经验水平?

新手水平 中等水平 熟练水平

设置 Flutter 开发环境

  1. 在计算机上安装 Flutter SDK
  2. 打开 Android Studio,然后安装适用于 Android Studio 的 Flutter 扩展程序

下载代码

下载源代码 在 GitHub 上查看代码库

下载 ZIP 文件后,解压其内容。您将会看到名为 admob-inline-ads-in-flutter-main 的文件夹。

或者,您也可以从命令行克隆 GitHub 代码库:

$ git clone https://github.com/googlecodelabs/admob-inline-ads-in-flutter

该代码库包含三个文件夹:

  • android_studio_folder.png starter:您将在此 Codelab 中构建的入门级代码。
  • android_studio_folder.png complete:此 Codelab 完成后的代码。(Java 和 Objective-C 版原生代码)
  • android_studio_folder.png complete_kotlin_swift:此 Codelab 完成后的代码。(Kotlin 和 Swift 版原生代码)

由于 Flutter 是一个多平台 SDK,因此您需要在 AdMob 中添加对 Android 和 iOS 都适用的应用和广告单元。

针对 Android 进行设置

如需针对 Android 进行设置,您需要添加 iOS 应用并创建广告单元。

添加 Android 应用

  1. AdMob 控制台中,点击应用菜单中的添加应用
  2. 当系统询问“您是否已在 Google Play 或 App Store 中发布您的应用?”时,点击
  3. 在应用名称字段中输入 AdMob inline ads,然后选择 Android 作为平台。

e4d968797610c8c0.pngS

  1. 您不必启用用户指标就能完成此 Codelab。不过,我们仍建议您启用用户指标,以便更详细地了解用户行为。点击添加便可完成此流程。

5204925f5c652b41.png

创建广告单元

如需添加广告单元,请按以下步骤操作:

  1. AdMob 控制台的应用菜单中,选择 AdMob 内嵌广告应用。
  2. 点击广告单元菜单。

横幅广告

  1. 点击添加广告单元
  2. 选择横幅广告作为格式。
  3. 广告单元名称字段中输入 android-inline-banner
  4. 点击创建广告单元便可完成此流程。

原生广告

  1. 点击添加广告单元
  2. 选择原生高级广告作为格式。
  3. 广告单元名称字段中输入 android-inline-native
  4. 点击创建广告单元便可完成此流程。

新的广告单元需要过几小时才能开始投放广告。

如果您想立即测试广告的行为,请使用“Android 应用/广告单元 ID”表格中列出的测试应用 ID 和广告单元 ID。

针对 iOS 进行设置

如需针对 iOS 进行设置,您需要添加 iOS 应用并创建广告单元。

添加 iOS 应用

  1. AdMob 控制台中,点击应用菜单中的添加应用
  2. 当系统询问“您是否已在 Google Play 或 App Store 中发布您的应用?”时,点击
  3. 在应用名称字段中输入 AdMob inline ads,然后选择 iOS 作为平台。

e00fc8ac930a251b.png

  1. 您不必启用用户指标就能完成此 Codelab。不过,我们仍建议您启用用户指标,以便更详细地了解用户行为。点击添加便可完成此流程。

5204925f5c652b41.png

创建广告单元

如需添加广告单元,请按以下步骤操作:

  1. AdMob 控制台的应用菜单中,选择 AdMob 内嵌广告应用。
  2. 点击广告单元菜单。

横幅广告

  1. 点击添加广告单元
  2. 选择横幅广告作为格式。
  3. 广告单元名称字段中输入 ios-inline-banner
  4. 点击创建广告单元便可完成此流程。

原生广告

  1. 点击添加广告单元
  2. 选择原生高级广告作为格式。
  3. 广告单元名称字段中输入 ios-inline-native
  4. 点击创建广告单元便可完成此流程。

新的广告单元需要过几小时才能开始投放广告。

如果您想立即测试广告的行为,请使用下表中列出的测试应用 ID 和广告单元 ID。

可选:使用测试 AdMob 应用和广告单元

如果您希望按照此 Codelab 操作,而不是自行创建新的应用和广告单元,可以使用下表列出的测试 AdMob 应用 ID 和广告单元 ID。

Android 应用 ID/广告单元 ID

项目

应用 ID/广告单元 ID

AdMob 应用 ID

ca-app-pub-3940256099942544~3347511713

横幅广告

ca-app-pub-3940256099942544/6300978111

原生广告

ca-app-pub-3940256099942544/2247696110

iOS 应用 ID/广告单元 ID

项目

应用 ID/广告单元 ID

AdMob 应用 ID

ca-app-pub-3940256099942544~1458002511

横幅广告

ca-app-pub-3940256099942544/2934735716

原生广告

ca-app-pub-3940256099942544/3986624511

如需详细了解测试广告,请参阅 Android 测试广告iOS 测试广告开发者文档。

Flutter 使用插件提供对各类平台专用服务的访问权限。借助插件,您可以访问各个平台上的服务和 API。

google_mobile_ads 插件支持使用 AdMob API 加载和展示横幅广告、插页式广告、激励广告和原生广告。

由于 Flutter 是一个多平台 SDK,因此 google_mobile_ads 插件既适用于 iOS 也适用于 Android。因此,如果您将该插件添加到 Flutter 应用,Android 版和 iOS 版 AdMob 内嵌广告应用都会使用该插件。

将 Google 移动广告插件添加为依赖项

如需从 AdMob 内嵌广告项目访问 AdMob API,请将 google_mobile_ads 作为依赖项添加到项目根目录下的 pubspec.yaml 文件中。

pubspec.yaml

...
environment:
  # TODO: Update the minimum sdk version to 2.12.0 to support null safety.
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  google_fonts: ^0.3.9

  # TODO: Add google_mobile_ads as a dependency
  google_mobile_ads: ^0.12.0

...

点击 Pub get,以在 AdMob 内嵌广告项目中安装该插件。

39e66c2a3b7f078b.png

更新 AndroidManifest.xml (Android)

  1. 在 Android Studio 中打开 android/app/src/main/AndroidManifest.xml 文件。
  2. 添加 <meta-data> 标签并将名称指定为 com.google.android.gms.ads.APPLICATION_ID,以 添加您的 AdMob 应用 ID。例如,如果您的 AdMob 应用 ID 为 ca-app-pub-3940256099942544~3347511713,则需要将以下行添加到 AndroidManifest.xml 文件中。

AndroidManifest.xml

<manifest>
    ...
    <application>
       ...
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-3940256099942544~3347511713"/>
    </application>

</manifest>

更新 Info.plist (iOS)

  1. 在 Android Studio 中打开 ios/Runner/Info.plist 文件。
  2. 添加一个 GADApplicationIdentifier 键,并将 AdMob 应用 ID 设为相应字符串值。例如,如果您的 AdMob 应用 ID 为 ca-app-pub-3940256099942544~1458002511,则需要将以下行添加到 Info.plist 文件中。

ios/Runner/Info.plist

...
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string>
...

lib 目录下创建名为 ad_helper.dart 的新文件。然后,实现 AdHelper 类,该类会提供对 Android 和 iOS 都适用的 AdMob 应用 ID 和广告单元 ID。

请务必将 AdMob 应用 ID (ca-app-pub-xxxxxx~yyyyy) 和广告单元 ID (ca-app-pub-xxxxxxx/yyyyyyyy) 替换为您在上一步中创建的 ID。

ad_helper.dart

import 'dart:io';

class AdHelper {

  static String get bannerAdUnitId {
    if (Platform.isAndroid) {
      return "<YOUR_ANDROID_BANNER_AD_UNIT_ID";
    } else if (Platform.isIOS) {
      return "<YOUR_IOS_BANNER_AD_UNIT_ID>";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }

  static String get nativeAdUnitId {
    if (Platform.isAndroid) {
      return "<YOUR_ANDROID_NATIVE_AD_UNIT_ID>";
    } else if (Platform.isIOS) {
      return "<YOUR_IOS_NATIVE_AD_UNIT_ID>";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }
}

如果您希望使用测试 AdMob 应用 ID 和测试广告单元 ID,请使用以下代码段。

ad_helper.dart

import 'dart:io';

class AdHelper {

  static String get bannerAdUnitId {
    if (Platform.isAndroid) {
      return 'ca-app-pub-3940256099942544/6300978111';
    } else if (Platform.isIOS) {
      return 'ca-app-pub-3940256099942544/2934735716';
    }
    throw new UnsupportedError("Unsupported platform");
  }

  static String get nativeAdUnitId {
    if (Platform.isAndroid) {
      return 'ca-app-pub-3940256099942544/2247696110';
    } else if (Platform.isIOS) {
      return 'ca-app-pub-3940256099942544/3986624511';
    }
    throw new UnsupportedError("Unsupported platform");
  }
}

加载广告之前,您需要初始化 Google 移动广告 SDK。打开 lib/home_page.dart 文件,并修改 _initGoogleMobileAds() 以在加载首页之前初始化 SDK。

请注意,您需要将 _initGoogleMobileAds() 方法的返回值类型从 Future<dynamic> 更改为 Future<InitializationStatus>,以在完成后获取 SDK 初始化结果。

home_page.dart

// TODO: Import google_mobile_ads.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';

import 'package:flutter/material.dart';

...

class HomePage extends StatelessWidget {

  ...

  Future<InitializationStatus> _initGoogleMobileAds() {
    // TODO: Initialize Google Mobile Ads SDK
    return MobileAds.instance.initialize();
  }
}

在该部分中,您将在列表中间展示横幅广告,如下面的屏幕截图所示。

a3cd508128793e41.png

  1. 打开 lib/banner_inline_page.dart 文件。
  2. 添加以下行以导入 ad_helper.dartgoogle_mobile_ads.dart
...

// TODO: Import ad_helper.dart
import 'package:admob_inline_ads_in_flutter/ad_helper.dart';

// TODO: Import google_mobile_ads.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';

class BannerInlinePage extends StatefulWidget {
  ...
}
  1. _BannerInlinePageState 类中,为横幅广告添加以下组件和方法。

请注意,_kAdIndex 表示将展示横幅广告的索引,此索引可用于通过 _getDestinationItemIndex() 方法计算项目索引。

banner_inline_page.dart

class _BannerInlinePageState extends State<BannerInlinePage> {

  // TODO: Add _kAdIndex
  static final _kAdIndex = 4;

  // TODO: Add a BannerAd instance
  late BannerAd _ad;

  // TODO: Add _isAdLoaded
  bool _isAdLoaded = false;

  ...

  // TODO: Add _getDestinationItemIndex()
  int _getDestinationItemIndex(int rawIndex) {
    if (rawIndex >= _kAdIndex && _isAdLoaded) {
      return rawIndex - 1;
    }
    return rawIndex;
  }

  ...
}
  1. initState() 方法中,为 320x50 横幅广告 (AdSize.banner) 创建并加载 BannerAd。请注意,广告事件监听器已配置在加载广告 (MobileAdEvent.loaded) 后更新界面 (setState())。

banner_inline_page.dart

@override
void initState() {
  super.initState();

  // TODO: Create a BannerAd instance
  _ad = BannerAd(
    adUnitId: AdHelper.bannerAdUnitId,
    size: AdSize.banner,
    request: AdRequest(),
    listener: AdListener(
      onAdLoaded: (_) {
        setState(() {
          _isAdLoaded = true;
        });
      },
      onAdFailedToLoad: (ad, error) {
        // Releases an ad resource when it fails to load
        ad.dispose();

        print('Ad load failed (code=${error.code} message=${error.message})');
      },
    ),
  );

  // TODO: Load an ad
  _ad.load();
}
  1. 修改 build() 方法,以展示横幅广告(如果可用)。
  2. 更新 itemCount, 以计为一个横幅广告条目,并更新 itemBuilder, 以便加载横幅广告后在广告索引 (_kAdIndex) 处呈现该广告。
  3. 更新代码,以使用 _getDestinationItemIndex() 方法检索内容项的索引。

banner_inline_page.dart

@override
Widget build(BuildContext context) {
  return Scaffold(
    ...
    body: ListView.builder(
      // TODO: Adjust itemCount based on the ad load state
      itemCount: widget.entries.length + (_isAdLoaded ? 1 : 0),
      itemBuilder: (context, index) {
        // TODO: Render a banner ad
        if (_isAdLoaded && index == _kAdIndex) {
          return Container(
            child: AdWidget(ad: _ad),
            width: _ad.size.width.toDouble(),
            height: 72.0,
            alignment: Alignment.center,
          );
        } else {
          // TODO: Get adjusted item index from _getDestinationItemIndex()
          final item = widget.entries[_getDestinationItemIndex(index)];

          return ListTile(
            ...
          );
        }
      },
    ),
  );
}
  1. 调用 dispose() 回调方法中的 BannerAd.dispose() 方法,释放与 BannerAd 对象关联的资源。

banner_inline_page.dart

@override
void dispose() {
  // TODO: Dispose a BannerAd object
  _ad.dispose();

  super.dispose();
}

大功告成!运行项目,然后点击首页上的横幅内嵌广告按钮。广告加载后,您将在列表中间看到横幅广告。

f1372268878a80a2.png c32af50872514224.png

在该部分中,您将在列表中间展示原生广告,如下面的屏幕截图所示。

fbe594d5d9ce08ea.png

系统使用平台原生的界面组件(例如,Android 上的 View 或 iOS 上的 UIView)向用户展示原生广告。

不过,您无法使用 Flutter 微件直接创建原生界面组件。因此,您必须针对各平台实现 NativeAdFactory,此类可用于通过原生广告对象(Android 上的 UnifiedNativeAd 和 iOS 上的 GADUnifiedNativeAd)构建平台专用原生广告视图(Android 上的 UnifiedNativeAdView 和 iOS 上的 GADUnifiedNativeAdView)。

针对 Android (Java) 实现 NativeAdFactory

  1. 打开 android/build.gradle 文件(或 android 文件夹下的任何文件),然后点击 Open in Edit for Android Studio 以打开 Android 项目。

88d3e99426cd8d8c.png

  1. 如果系统要求您选择一个窗口以打开新项目,请点击 New Window,以确保您在处理 Android 项目时保持 Flutter 项目处于打开状态。

5d554e50a83f2bd1.png

创建原生广告布局

  1. 打开 Android 项目后,在 Android Studio 的项目窗格中右键点击 app,然后从上下文菜单中依次选择 New > Android Resource File

7b2b6d28d0fe80a9.png

  1. New Resource File 对话框中,输入 list_tile_native_ad.xml 作为文件名。
  2. 选择 Layout 作为资源类型,并输入 com.google.android.gms.ads.formats.UnifiedNativeAdView 作为根元素。
  3. 点击 OK 以创建新的布局文件。

615bf550a2ce9c45.png

  1. 按以下方式实现广告布局。请注意,布局应该与布局面向的平台的用户体验视觉设计相匹配。

list_tile_native_ad.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.gms.ads.formats.UnifiedNativeAdView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_list_tile_native_ad_attribution_small"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#F19938"
            android:text="Ad"
            android:textColor="#FFFFFF"
            android:textSize="12sp" />

        <ImageView
            android:id="@+id/iv_list_tile_native_ad_icon"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:scaleType="fitXY"
            tools:background="#EDEDED" />

        <TextView
            android:id="@+id/tv_list_tile_native_ad_attribution_large"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:background="#F19938"
            android:gravity="center"
            android:text="Ad"
            android:textColor="#FFFFFF"
            android:visibility="invisible" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="80dp"
            android:layout_marginLeft="80dp"
            android:layout_marginEnd="16dp"
            android:layout_marginRight="16dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_list_tile_native_ad_headline"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:lines="1"
                android:maxLines="1"
                android:textColor="#000000"
                android:textSize="16sp"
                tools:text="Headline" />

            <TextView
                android:id="@+id/tv_list_tile_native_ad_body"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:lines="1"
                android:maxLines="1"
                android:textColor="#828282"
                android:textSize="14sp"
                tools:text="body" />

        </LinearLayout>

    </FrameLayout>

</com.google.android.gms.ads.formats.UnifiedNativeAdView>

创建 ListTileNativeAdFactory 类

  1. 在 Project 窗格中,右键点击 com.codelab.flutter.admobinlineads 软件包,然后依次选择 New > Java Class

609df24b6a0b647e.png

  1. 输入 ListTileNativeAdFactory 作为名称,然后从列表中选择 Class

642050a1a83d5033.png

  1. 出现 New Class 对话框后,将所有字段留空,然后点击 OK

您会看到软件包 com.codelab.flutter.admobinlineads 中已创建 ListTileNativeAdFactory 类。

3e7c27d864686efa.png

  1. 按如下方式实现 ListTileNativeAdFactory 类。请注意,该类会在 GoogleMobileAdsPlugin.NativeAdFactory 接口中实现 createNativeAd() 方法。

该工厂类负责创建用于呈现原生广告的视图对象。从代码可以看出,该工厂类创建了一个 UnitifedNativeAdView,然后用 UnifiedNativeAd 对象进行填充。

ListTileNativeAdFactory.java

// TODO: Implement ListTileNativeAdFactory class

package com.codelab.flutter.admobinlineads;

import com.google.android.gms.ads.formats.NativeAd;
import com.google.android.gms.ads.formats.UnifiedNativeAd;
import com.google.android.gms.ads.formats.UnifiedNativeAdView;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.Map;

import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin;

class ListTileNativeAdFactory implements GoogleMobileAdsPlugin.NativeAdFactory {

    private final Context context;

    ListTileNativeAdFactory(Context context) {
        this.context = context;
    }

    @Override
    public UnifiedNativeAdView createNativeAd(
            UnifiedNativeAd nativeAd, Map<String, Object> customOptions) {
        UnifiedNativeAdView nativeAdView = (UnifiedNativeAdView) LayoutInflater.from(context)
                .inflate(R.layout.list_tile_native_ad, null);

        TextView attributionViewSmall = nativeAdView
                .findViewById(R.id.tv_list_tile_native_ad_attribution_small);
        TextView attributionViewLarge = nativeAdView
                .findViewById(R.id.tv_list_tile_native_ad_attribution_large);

        ImageView iconView = nativeAdView.findViewById(R.id.iv_list_tile_native_ad_icon);
        NativeAd.Image icon = nativeAd.getIcon();
        if (icon != null) {
            attributionViewSmall.setVisibility(View.VISIBLE);
            attributionViewLarge.setVisibility(View.INVISIBLE);
            iconView.setImageDrawable(icon.getDrawable());
        } else {
            attributionViewSmall.setVisibility(View.INVISIBLE);
            attributionViewLarge.setVisibility(View.VISIBLE);
        }
        nativeAdView.setIconView(iconView);

        TextView headlineView = nativeAdView.findViewById(R.id.tv_list_tile_native_ad_headline);
        headlineView.setText(nativeAd.getHeadline());
        nativeAdView.setHeadlineView(headlineView);

        TextView bodyView = nativeAdView.findViewById(R.id.tv_list_tile_native_ad_body);
        bodyView.setText(nativeAd.getBody());
        bodyView.setVisibility(nativeAd.getBody() != null ? View.VISIBLE : View.INVISIBLE);
        nativeAdView.setBodyView(bodyView);

        nativeAdView.setNativeAd(nativeAd);

        return nativeAdView;
    }
}

注册 ListTileNativeAdFactory 类

NativeAdFactory 的实例应注册到 GoogleMobileAdsPlugin,然后才能从 Flutter 端使用。

  1. 打开 MainActivity.java 文件,并替换 configureFlutterEngine() 方法和 cleanUpFlutterEngine() 方法。
  2. configureFlutterEngine() 方法中使用唯一字符串 ID (listTile) 注册 ListTileNativeAdFactory 类。

MainActivity.java

public class MainActivity extends FlutterActivity {

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);

        // TODO: Register the ListTileNativeAdFactory
        GoogleMobileAdsPlugin.registerNativeAdFactory(flutterEngine, "listTile",
                new ListTileNativeAdFactory(getContext()));
    }

    ...
}
  1. 在清理流程中,应取消注册每个 NativeAdFactory 实例。取消注册 cleanUpFlutterEngine() 方法中的 ListTileNativeAdFactory 类。

MainActivity.java

public class MainActivity extends FlutterActivity {

    ...

    @Override
    public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.cleanUpFlutterEngine(flutterEngine);

        // TODO: Unregister the ListTileNativeAdFactory
        GoogleMobileAdsPlugin.unregisterNativeAdFactory(flutterEngine, "listTile");
    }
}

现在,您可以使用 ListTileNativeAdFactory 类在 Android 上呈现原生广告了。

针对 Android (Kotlin) 实现 NativeAdFactory

  1. 打开 android/build.gradle 文件(或 android 文件夹下的任何文件),然后点击 Open in Edit for Android Studio 以打开 Android 项目。

88d3e99426cd8d8c.png

  1. 如果系统要求您选择一个窗口以打开新项目,请点击 New Window,以确保您在处理 Android 项目时保持 Flutter 项目处于打开状态。

5d554e50a83f2bd1.png

创建原生广告布局

  1. 打开 Android 项目后,在 Android Studio 的项目窗格中右键点击 app,然后从上下文菜单中依次选择 New > Android Resource File

7b2b6d28d0fe80a9.png

  1. New Resource File 对话框中,输入 list_tile_native_ad.xml 作为文件名。
  2. 选择 Layout 作为资源类型,并输入 com.google.android.gms.ads.formats.UnifiedNativeAdView 作为根元素。
  3. 点击 OK 以创建新的布局文件。

615bf550a2ce9c45.png

  1. 按以下方式实现广告布局。请注意,布局应该与布局面向的平台的用户体验视觉设计相匹配。

list_tile_native_ad.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.gms.ads.formats.UnifiedNativeAdView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_list_tile_native_ad_attribution_small"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#F19938"
            android:text="Ad"
            android:textColor="#FFFFFF"
            android:textSize="12sp" />

        <ImageView
            android:id="@+id/iv_list_tile_native_ad_icon"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:scaleType="fitXY"
            tools:background="#EDEDED" />

        <TextView
            android:id="@+id/tv_list_tile_native_ad_attribution_large"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:background="#F19938"
            android:gravity="center"
            android:text="Ad"
            android:textColor="#FFFFFF"
            android:visibility="invisible" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="80dp"
            android:layout_marginLeft="80dp"
            android:layout_marginEnd="16dp"
            android:layout_marginRight="16dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_list_tile_native_ad_headline"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:lines="1"
                android:maxLines="1"
                android:textColor="#000000"
                android:textSize="16sp"
                tools:text="Headline" />

            <TextView
                android:id="@+id/tv_list_tile_native_ad_body"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:lines="1"
                android:maxLines="1"
                android:textColor="#828282"
                android:textSize="14sp"
                tools:text="body" />

        </LinearLayout>

    </FrameLayout>

</com.google.android.gms.ads.formats.UnifiedNativeAdView>

创建 ListTileNativeAdFactory 类

  1. 在 Project 窗格中,右键点击 com.codelab.flutter.admobinlineads 软件包,然后依次选择 New > Kotlin File/Class

7311744cb97cad75.png

  1. 输入 ListTileNativeAdFactory 作为名称,然后从列表中选择 Class

43a6ac7728826d8.png

  1. 您会看到软件包 com.codelab.flutter.admobinlineads 中已创建 ListTileNativeAdFactory 类。
  2. 按如下方式实现 ListTileNativeAdFactory 类。请注意,该类会在 GoogleMobileAdsPlugin.NativeAdFactory 接口中实现 createNativeAd() 方法。

该工厂类负责创建用于呈现原生广告的视图对象。从代码可以看出,该工厂类创建了一个 UnitifedNativeAdView,然后用 UnifiedNativeAd 对象进行填充。

ListTileNativeAdFactory.kt

// TODO: Implement ListTileNativeAdFactory class

package com.codelab.flutter.admobinlineads

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.google.android.gms.ads.formats.UnifiedNativeAd
import com.google.android.gms.ads.formats.UnifiedNativeAdView
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin

class ListTileNativeAdFactory(val context: Context) : GoogleMobileAdsPlugin.NativeAdFactory {

    override fun createNativeAd(
            nativeAd: UnifiedNativeAd,
            customOptions: MutableMap<String, Any>?
    ): UnifiedNativeAdView {
        val nativeAdView = LayoutInflater.from(context)
                .inflate(R.layout.list_tile_native_ad, null) as UnifiedNativeAdView

        with(nativeAdView) {
            val attributionViewSmall =
                    findViewById<TextView>(R.id.tv_list_tile_native_ad_attribution_small)
            val attributionViewLarge =
                    findViewById<TextView>(R.id.tv_list_tile_native_ad_attribution_large)

            val iconView = findViewById<ImageView>(R.id.iv_list_tile_native_ad_icon)
            val icon = nativeAd.icon
            if (icon != null) {
                attributionViewSmall.visibility = View.VISIBLE
                attributionViewLarge.visibility = View.INVISIBLE
                iconView.setImageDrawable(icon.drawable)
            } else {
                attributionViewSmall.visibility = View.INVISIBLE
                attributionViewLarge.visibility = View.VISIBLE
            }
            this.iconView = iconView

            val headlineView = findViewById<TextView>(R.id.tv_list_tile_native_ad_headline)
            headlineView.text = nativeAd.headline
            this.headlineView = headlineView

            val bodyView = findViewById<TextView>(R.id.tv_list_tile_native_ad_body)
            with(bodyView) {
                text = nativeAd.body
                visibility = if (nativeAd.body.isNotEmpty()) View.VISIBLE else View.INVISIBLE
            }
            this.bodyView = bodyView

            setNativeAd(nativeAd)
        }

        return nativeAdView
    }
}

注册 ListTileNativeAdFactory 类

NativeAdFactory 的实例应注册到 GoogleMobileAdsPlugin,然后才能从 Flutter 端使用。

  1. 打开 MainActivity.kt 文件,并替换 configureFlutterEngine() 方法和 cleanUpFlutterEngine() 方法。
  2. configureFlutterEngine() 方法中使用唯一字符串 ID (listTile) 注册 ListTileNativeAdFactory 类。

MainActivity.kt

class MainActivity: FlutterActivity() {

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        // TODO: Register the ListTileNativeAdFactory
        GoogleMobileAdsPlugin.registerNativeAdFactory(
                flutterEngine, "listTile", ListTileNativeAdFactory(context))
    }

    ...
}
  1. 在清理流程中,应取消注册每个 NativeAdFactory 实例。取消注册 cleanUpFlutterEngine() 方法中的 ListTileNativeAdFactory 类。

MainActivity.kt

class MainActivity: FlutterActivity() {
    ...

    override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) {
        super.cleanUpFlutterEngine(flutterEngine)

        // TODO: Unregister the ListTileNativeAdFactory
        GoogleMobileAdsPlugin.unregisterNativeAdFactory(flutterEngine, "listTile")
    }
}

现在,您可以使用 ListTileNativeAdFactory 类在 Android 上呈现原生广告了。

针对 iOS (Objective-C) 实现 NativeAdFactory

打开 ios/Podfile 文件(或 ios 文件夹中的任意文件),然后点击 Open iOS module in Xcode 以打开一个 iOS 项目。

8f8b417705df6741.png

准备原生广告布局

您需要配置自定义视图 (*.xib) 来布局原生广告素材资源。在此 Codelab 中,可借助预配置视图减轻工作量。

在 Xcode 中打开的 iOS 项目后,请确认 Runner 项目中存在 ListTileNativeAdView.xib

8551d919ccc41818.png

创建 ListTileNativeAdFactory 类

  1. 在项目导航器中,右键点击 Runner 组,然后选择 New File 以便为新类创建头文件。

249c43a7a1eac87.png

  1. 在模板对话框中,选择 Header File,并将其命名为 ListTileNativeAdFactory
  2. 创建 ListTileNativeAdFactory.h 文件后,按以下方式定义 ListNativeAdFactory 类:

ListTileNativeAdFactory.h

#ifndef ListTileNativeAdFactory_h
#define ListTileNativeAdFactory_h

// TODO: Import FLTGoogleMobileAdsPlugin.h
#import "FLTGoogleMobileAdsPlugin.h"

// TODO: Declare ListTileNativeAdFactory
@interface ListTileNativeAdFactory : NSObject<FLTNativeAdFactory>

@end

#endif /* ListTileNativeAdFactory_h */
  1. Runner 组中选择 New File 以创建 Objective-C 文件。
  2. 在下一个对话框中,在 File 字段中输入 ListTileNativeAdFactory,然后选择 Empty File 作为文件类型。

26f03ec58f26d1d2.png

  1. 点击 Next 后,系统会提示您选择要在其中创建新文件的文件夹。无需更改任何设置,点击 Create

c40f7080bdf513ff.png

  1. 按以下方式实现 ListTileNativeFactory 类。请注意,该类会在 FLTNativeAdFactory 协议中实现 createNativeAd() 方法。

该工厂类负责创建用于呈现原生广告的视图对象。从代码可以看出,该工厂类创建了一个 GADUnitifedNativeAdView,然后用 GADUnifiedNativeAd 对象进行填充。

ListTileNativeAdFactory.m

// TODO: Import ListTileNativeAdFactory.h
#import "ListTileNativeAdFactory.h"

// TODO: Implement ListTileNativeAdFactory
@implementation ListTileNativeAdFactory

- (GADUnifiedNativeAdView *)createNativeAd:(GADUnifiedNativeAd *)nativeAd
                             customOptions:(NSDictionary *)customOptions {
  GADUnifiedNativeAdView *nativeAdView =
    [[NSBundle mainBundle] loadNibNamed:@"ListTileNativeAdView" owner:nil options:nil].firstObject;

  ((UILabel *)nativeAdView.headlineView).text = nativeAd.headline;

  ((UILabel *)nativeAdView.bodyView).text = nativeAd.body;
  nativeAdView.bodyView.hidden = nativeAd.body ? NO : YES;

  ((UIImageView *)nativeAdView.iconView).image = nativeAd.icon.image;
  nativeAdView.iconView.hidden = nativeAd.icon ? NO : YES;

  nativeAdView.callToActionView.userInteractionEnabled = NO;

  nativeAdView.nativeAd = nativeAd;

  return nativeAdView;
}

@end

注册 ListTileNativeAdFacotry 类

FLTNativeAdFactory 的实现应注册到 FLTGoogleMobileAdsPlugin,然后才能从 Flutter 端使用。

打开 AppDelegate.m 文件,通过调用 [FLTGoogleMobileAdsPlugin registerNativeAdFactory] 方法,使用唯一字符串 ID (listTile) 注册 ListTileNativeAdFactory

AppDelegate.m

#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"

// TODO: Import ListTileNativeAdFactory.h
#import "ListTileNativeAdFactory.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];

  // TODO: Register ListTileNativeAdFactory
  ListTileNativeAdFactory *listTileFactory = [[ListTileNativeAdFactory alloc] init];
  [FLTGoogleMobileAdsPlugin registerNativeAdFactory:self
                                        factoryId:@"listTile"
                                  nativeAdFactory:listTileFactory];

  // Override point for customization after application launch.
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

现在,您可以使用 ListTileNativeAdFactory 在 iOS 上呈现原生广告了。

针对 iOS (Swift) 实现 NativeAdFactory

打开 ios/Podfile 文件(或 ios 文件夹中的任意文件),然后点击 Open iOS module in Xcode 以打开一个 iOS 项目。

8f8b417705df6741.png

准备原生广告布局

您需要配置自定义视图 (*.xib) 来布局原生广告素材资源。在此 Codelab 中,可借助预配置视图减轻工作量。

在 Xcode 中打开的 iOS 项目后,请确认 Runner 项目中存在 ListTileNativeAdView.xib

8551d919ccc41818.png

创建 ListTileNativeAdFactory 类

  1. 在项目导航器中,右键点击 Runner 组,然后选择 New File 以便为新类创建头文件。

90bdca063c491134.png

  1. 在模板对话框中,选择 Swift File,并将其命名为 ListTileNativeAdFactory
  2. 创建 ListTileNativeAdFactory.swift 文件后,请实现 ListNativeAdFactory 类。

请注意,该类会在 FLTNativeAdFactory 协议中实现 createNativeAd() 方法。

该工厂类负责创建用于呈现原生广告的视图对象。从代码可以看出,该工厂类创建了一个 GADUnitifedNativeAdView,然后用 GADUnifiedNativeAd 对象进行填充。

ListTileNativeAdFactory.swift

// TODO: Import google_mobile_ads
import google_mobile_ads

// TODO: Implement ListTileNativeAdFactory
class ListTileNativeAdFactory : FLTNativeAdFactory {

    func createNativeAd(_ nativeAd: GADUnifiedNativeAd,
                        customOptions: [AnyHashable : Any]? = nil) -> GADUnifiedNativeAdView? {
        let nibView = Bundle.main.loadNibNamed("ListTileNativeAdView", owner: nil, options: nil)!.first
        let nativeAdView = nibView as! GADUnifiedNativeAdView

        (nativeAdView.headlineView as! UILabel).text = nativeAd.headline

        (nativeAdView.bodyView as! UILabel).text = nativeAd.body
        nativeAdView.bodyView!.isHidden = nativeAd.body == nil

        (nativeAdView.iconView as! UIImageView).image = nativeAd.icon?.image
        nativeAdView.iconView!.isHidden = nativeAd.icon == nil

        nativeAdView.callToActionView?.isUserInteractionEnabled = false

        nativeAdView.nativeAd = nativeAd

        return nativeAdView
    }
}

注册 ListTileNativeAdFacotry 类

FLTNativeAdFactory 的实现应注册到 FLTGoogleMobileAdsPlugin,然后才能从 Flutter 端使用。

打开 AppDelegate.m 文件,通过调用 FLTGoogleMobileAdsPlugin.registerNativeAdFactory() 方法,使用唯一字符串 ID (listTile) 注册 ListTileNativeAdFactory

AppDelegate.swift

import UIKit
import Flutter

// TODO: Import google_mobile_ads
import google_mobile_ads

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)

    // TODO: Register ListTileNativeAdFactory
    let listTileFactory = ListTileNativeAdFactory()
    FLTGoogleMobileAdsPlugin.registerNativeAdFactory(
        self, factoryId: "listTile", nativeAdFactory: listTileFactory)

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

现在,您可以使用 ListTileNativeAdFactory 在 iOS 上呈现原生广告了。

将原生广告与 Flutter 微件集成

  1. 打开 lib/native_inline_page.dart 文件。然后,通过添加以下行导入 ad_helper.dartgoogle_mobile_ads.dart

native_inline_page.dart

...

// TODO: Import ad_helper.dart
import 'package:admob_inline_ads_in_flutter/ad_helper.dart';

// TODO: Import google_mobile_ads.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';

class NativeInlinePage extends StatefulWidget {
  ...
}
  1. _NativeInlinePageState 类中,为原生广告添加以下组件和方法。

请注意,_kAdIndex 表示将展示横幅广告的索引,此索引可用于通过 _getDestinationItemIndex() 方法计算项目索引。

native_inline_page.dart

class _NativeInlinePageState extends State<NativeInlinePage> {

  // TODO: Add _kAdIndex
  static final _kAdIndex = 4;

  // TODO: Add a NativeAd instance
  late NativeAd _ad;

  // TODO: Add _isAdLoaded
  bool _isAdLoaded = false;

  ...

  // TODO: Add _getDestinationItemIndex()
  int _getDestinationItemIndex(int rawIndex) {
    if (rawIndex >= _kAdIndex && _isAdLoaded) {
      return rawIndex - 1;
    }
    return rawIndex;
  }

  ...
}
  1. initState() 方法中,创建并加载使用 ListTileNativeAdFactory 生成原生广告视图的 NativeAd

请注意,以下代码中使用了用于将工厂注册到插件的同个工厂 ID (listTile)。

native_inline_page.dart

@override
void initState() {
  super.initState();

  // TODO: Create a NativeAd instance
  _ad = NativeAd(
    adUnitId: AdHelper.nativeAdUnitId,
    factoryId: 'listTile',
    request: AdRequest(),
    listener: AdListener(
      onAdLoaded: (_) {
        setState(() {
          _isAdLoaded = true;
        });
      },
      onAdFailedToLoad: (ad, error) {
        // Releases an ad resource when it fails to load
        ad.dispose();

        print('Ad load failed (code=${error.code} message=${error.message})');       },
    ),
  );

  _ad.load();
}
  1. 修改 build() 方法,以展示横幅广告(如果可用)。
  2. 更新 itemCount, 以计为一个横幅广告条目,并更新 itemBuilder, 以便加载横幅广告后在广告索引 (_kAdIndex) 处呈现该广告。
  3. 更新代码,以使用 _getDestinationItemIndex() 方法检索内容项的索引。

native_inline_page.dart

@override
Widget build(BuildContext context) {
  return Scaffold(
    ...
    body: ListView.builder(
      // TODO: Adjust itemCount based on the ad load state
      itemCount: widget.entries.length + (_isAdLoaded ? 1 : 0),
      itemBuilder: (context, index) {
        // TODO: Render a banner ad
        if (_isAdLoaded && index == _kAdIndex) {
          return Container(
            child: AdWidget(ad: _ad),
            height: 72.0,
            alignment: Alignment.center,
          );
        } else {
          // TODO: Get adjusted item index from _getDestinationItemIndex()
          final item = widget.entries[_getDestinationItemIndex(index)];

          return ListTile(
            ...
          );
        }
      },
    ),
  );
}
  1. 调用 dispose() 回调方法中的 NativeAd.dispose() 方法,释放与 NativeAd 对象关联的资源。

native_inline_page.dart

@override
void dispose() {
  // TODO: Dispose a NativeAd object
  _ad.dispose();

  super.dispose();
}

大功告成!运行项目,然后点击首页上的原生内嵌广告按钮。广告加载后,您将在列表中间看到原生广告。

fbe594d5d9ce08ea.png 5ead873222c800eb.png

您已完成此 Codelab。您可以在 android_studio_folder.pngcompleteandroid_studio_folder.png complete_kotlin_swift 文件夹中找到此 Codelab 的完整代码。