├── .github └── workflows │ ├── flutter.yml │ └── release.yml ├── .gitignore ├── .pubignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── zero │ │ └── flutter_qq_ads │ │ ├── FlutterQqAdsPlugin.java │ │ ├── PluginDelegate.java │ │ ├── event │ │ ├── AdErrorEvent.java │ │ ├── AdEvent.java │ │ ├── AdEventAction.java │ │ ├── AdEventHandler.java │ │ └── AdRewardEvent.java │ │ ├── load │ │ ├── FeedAdLoad.java │ │ └── FeedAdManager.java │ │ ├── page │ │ ├── AdBannerView.java │ │ ├── AdFeedView.java │ │ ├── AdSplashActivity.java │ │ ├── BaseAdPage.java │ │ ├── InterstitialPage.java │ │ ├── NativeViewFactory.java │ │ └── RewardVideoPage.java │ │ └── utils │ │ └── UIUtils.java │ └── res │ ├── layout │ └── activity_ad_splash.xml │ ├── mipmap-xxhdpi │ └── flutterads_logo.png │ └── values │ ├── strings.xml │ └── themes.xml ├── doc └── SETTING_LOGO.md ├── example ├── .gitignore ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ ├── keystore │ │ │ └── flutterads_key.jks │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ ├── com │ │ │ │ │ └── zero │ │ │ │ │ │ └── flutter_qq_ads_example │ │ │ │ │ │ └── MainActivity.java │ │ │ │ └── io │ │ │ │ │ └── flutter │ │ │ │ │ └── app │ │ │ │ │ └── FlutterMultiDexApplication.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── flutterads_logo.png │ │ │ │ ├── flutterads_logo2.png │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── images │ ├── gromore_1.png │ ├── gromore_2.png │ ├── gromore_pro.png │ └── pro.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon-1024.png │ │ │ ├── icon-20-ipad.png │ │ │ ├── icon-20@2x-ipad.png │ │ │ ├── icon-20@2x.png │ │ │ ├── icon-20@3x.png │ │ │ ├── icon-29-ipad.png │ │ │ ├── icon-29.png │ │ │ ├── icon-29@2x-ipad.png │ │ │ ├── icon-29@2x.png │ │ │ ├── icon-29@3x.png │ │ │ ├── icon-40.png │ │ │ ├── icon-40@2x.png │ │ │ ├── icon-40@3x.png │ │ │ ├── icon-60@2x.png │ │ │ ├── icon-60@3x.png │ │ │ ├── icon-76.png │ │ │ ├── icon-76@2x.png │ │ │ └── icon-83.5@2x.png │ │ ├── Contents.json │ │ ├── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage@1x.png │ │ │ ├── LaunchImage@2x.png │ │ │ └── LaunchImage@3x.png │ │ └── LaunchImage2.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage2@1x.png │ │ │ ├── LaunchImage2@2x.png │ │ │ └── LaunchImage2@3x.png │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── lib │ ├── ads_config.dart │ ├── feed_page.dart │ ├── home_page.dart │ ├── main.dart │ ├── pages │ │ ├── banner_page.dart │ │ ├── feed_page.dart │ │ ├── home_page.dart │ │ ├── interstitial_page.dart │ │ ├── pro_page.dart │ │ ├── reward_video_page.dart │ │ └── splash_page.dart │ ├── router │ │ └── router.dart │ ├── theme │ │ └── style.dart │ └── widgets │ │ └── widgets.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── go.mod ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── Event │ │ ├── FAQAdErrorEvent.h │ │ ├── FAQAdErrorEvent.m │ │ ├── FAQAdEvent.h │ │ ├── FAQAdEvent.m │ │ ├── FAQAdEventAction.h │ │ ├── FAQAdEventAction.m │ │ ├── FAQAdRewardEvent.h │ │ └── FAQAdRewardEvent.m │ ├── FlutterQqAdsPlugin.h │ ├── FlutterQqAdsPlugin.m │ ├── Load │ │ ├── FAQFeedAdLoad.h │ │ ├── FAQFeedAdLoad.m │ │ ├── FAQFeedAdManager.h │ │ └── FAQFeedAdManager.m │ └── Page │ │ ├── FAQAdBannerView.h │ │ ├── FAQAdBannerView.m │ │ ├── FAQAdFeedView.h │ │ ├── FAQAdFeedView.m │ │ ├── FAQBaseAdPage.h │ │ ├── FAQBaseAdPage.m │ │ ├── FAQInterstitialPage.h │ │ ├── FAQInterstitialPage.m │ │ ├── FAQNativeViewFactory.h │ │ ├── FAQNativeViewFactory.m │ │ ├── FAQRewardVideoPage.h │ │ ├── FAQRewardVideoPage.m │ │ ├── FAQSplashPage.h │ │ └── FAQSplashPage.m └── flutter_qq_ads.podspec ├── lib ├── event │ ├── ad_error_event.dart │ ├── ad_event.dart │ ├── ad_event_action.dart │ ├── ad_event_handler.dart │ └── ad_reward_event.dart ├── flutter_qq_ads.dart ├── flutter_qq_ads_method_channel.dart ├── flutter_qq_ads_platform_interface.dart └── view │ ├── ad_banner_widget.dart │ └── ad_feed_widget.dart ├── pubspec.yaml └── test ├── ads_config.dart └── flutter_qq_ads_test.dart /.github/workflows/flutter.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | # This workflow is triggered on pushes to the repository. 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | build: 12 | # This job will run on macos virtual machine 13 | runs-on: macos-13 14 | steps: 15 | 16 | # Setup Java environment in order to build the Android app. 17 | - uses: actions/checkout@v3 18 | - uses: actions/setup-java@v3 19 | with: 20 | distribution: 'temurin' 21 | java-version: '17' 22 | cache: 'gradle' 23 | # Setup the flutter environment. 24 | - uses: subosito/flutter-action@v2 25 | with: 26 | channel: 'stable' # 'dev', 'alpha', default to: 'stable' 27 | # flutter-version: '1.22.x' # you can also specify exact version of flutter 28 | 29 | # Get flutter dependencies. 30 | - run: flutter pub get 31 | 32 | # Check for any formatting issues in the code. 33 | - run: dart format --set-exit-if-changed . 34 | 35 | # Statically analyze the Dart code for any errors. 36 | - run: dart analyze . 37 | 38 | ##### 39 | # Build app 40 | ##### 41 | # Build apk. 42 | - run: cd example && flutter build apk 43 | # Build ios 44 | - run: cd example && flutter build ios --no-codesign 45 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | jobs: 9 | create_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Extract version from tag 16 | id: extract_version 17 | run: | 18 | VERSION=${GITHUB_REF#refs/tags/v} 19 | echo "VERSION=$VERSION" >> $GITHUB_ENV 20 | 21 | - name: Read release notes 22 | id: release_notes 23 | run: | 24 | VERSION=${{ env.VERSION }} 25 | CHANGELOG=$(sed -n "/## $VERSION/,/^## /p" CHANGELOG.md | sed '$d' | tail -n +2) 26 | if [ -z "$CHANGELOG" ]; then 27 | echo "Release notes not found for version $VERSION" 28 | exit 1 29 | fi 30 | echo "RELEASE_NOTES<> $GITHUB_ENV 31 | echo "$CHANGELOG" >> $GITHUB_ENV 32 | echo "EOF" >> $GITHUB_ENV 33 | 34 | - name: Create GitHub release 35 | uses: softprops/action-gh-release@v2 36 | with: 37 | body: ${{ env.RELEASE_NOTES }} 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | example/images 2 | example/android 3 | example/ios -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.9.0 2 | * 🚀 推荐使用【[Gromore 聚合变现](https://flutterads.top/)】 让您的广告收益最大化 3 | * 📱 Android SDK `v4.620.1490` 4 | * 🍎 iOS SDK `v4.15.00` 5 | * 🔐 迁移升级到 `plugin_platform_interface` 6 | 7 | ## 2.8.0 8 | * 🚀 推荐使用【[Gromore](https://flutterads.top/)】 让您的广告收益最大化 9 | * 📱 Android SDK `v4.580.1450` 10 | * 🍎 iOS SDK `v4.14.90` 11 | * 🔐 适配 iOS 17 隐私清单政策,防止上架被拒 12 | 13 | ## 2.7.0 14 | * 🚀 推荐使用【[Gromore](https://flutterads.top/)】 让您的广告收益最大化 15 | * [更新] Android SDK `v4.542.1412` 16 | * [更新] iOS SDK `v4.14.45` 17 | 18 | ## 2.6.0 19 | * [更新] Android SDK `v4.540.1410` 20 | * [更新] iOS SDK `v4.14.40` 21 | * [新增] 设置个性化广告接口 `setPersonalizedState` 22 | * [优化] 去掉信息流中自动适配高度的功能,由开发者自行设置高度 23 | * [优化] 去掉过时的方法调用 24 | ## 2.5.0 25 | * 更新 Android SDK `v4.460.1330` 26 | * 以后将不在维护非 1x 版本,请及时升级 27 | 28 | ## 2.4.1 29 | * 修复 iOS 编译问题 30 | 31 | ## 2.4.0 32 | * 新增 `信息流` 广告 33 | * 优化 `Banner` 广告内部增加宽高设置,不在需要外部嵌套一层约束组件 34 | * 优化统一底层架构 35 | * 更新 Android SDK `v4.430.1300` 36 | 37 | ## 2.3.0 38 | * 修复同时引入 [flutter_pangle_ads](https://github.com/FlutterAds/flutter_pangle_ads) 依赖时 iOS 无法编译的问题 #11 39 | * 优化底层架构 40 | * 更新 Android SDK `v4.410.1280` 41 | 42 | ## 2.2.0 43 | * 新增 Banner 广告 44 | * 项目增加 CI/CD 检查和自动发布,提高项目质量 45 | 46 | ## 2.1.2 47 | * 开屏增加超时时间设置 'fetchDelay' #9 48 | * 开屏 API 变更,logo 参数由位置参数变为可选命名参数,注意修改 49 | 50 | ## 2.1.1 51 | * 修复 iOS 使用 use_frameworks! 时编译的错误 #6 52 | 53 | ## 2.1.0 54 | * 新增插屏全屏视频广告 55 | * 新增插屏激励视频广告 56 | * 更新说明文档-新增事件说明 57 | 58 | ## 2.0.1 59 | * 更新 Android SDK v4.400.1270 60 | * 优化封装事件发送 61 | * 更新说明文档 62 | 63 | ## 2.0.0 64 | * 1.x.x 是非 Null Safety 版本 65 | * 2.x.x 是 Null Safety 版本 66 | * 现在阶段会同时维护这 2 个版本,再往后可能仅维护一个版本 67 | 68 | ## 1.5.0 69 | * 更新 Android SDK `v4.460.1330` 70 | * 以后将不在维护非 1x 版本,请及时升级 71 | 72 | ## 1.4.1 73 | * 修复 iOS 编译问题 74 | 75 | ## 1.4.0 76 | * 新增 `信息流` 广告 77 | * 优化 `Banner` 广告内部增加宽高设置,不在需要外部嵌套一层约束组件 78 | * 优化统一底层架构 79 | * 更新 Android SDK `v4.430.1300` 80 | 81 | ## 1.3.0 82 | * 修复同时引入 [flutter_pangle_ads](https://github.com/FlutterAds/flutter_pangle_ads) 依赖时 iOS 无法编译的问题 #11 83 | * 优化底层架构 84 | * 更新 Android SDK `v4.410.1280` 85 | 86 | ## 1.2.0 87 | * 新增 Banner 广告 88 | * 项目增加 CI/CD 检查和自动发布,提高项目质量 89 | 90 | ## 1.1.2 91 | * 开屏增加超时时间设置 'fetchDelay' #9 92 | * 开屏 API 变更,logo 参数由位置参数变为可选命名参数,注意修改 93 | 94 | ## 1.1.1 95 | * 修复 iOS 使用 use_frameworks! 时编译的错误 #6 96 | 97 | ## 1.1.0 98 | * 新增插屏全屏视频广告 99 | * 新增插屏激励视频广告 100 | * 更新说明文档-新增事件说明 101 | 102 | ## 1.0.1 103 | * 更新 Android SDK v4.400.1270 104 | * 优化封装事件发送 105 | * 更新说明文档 106 | 107 | ## 1.0.0 [2021-08-21] 108 | * 新增插屏广告 109 | * 新增激励视频广告 110 | * 优化补充说明文档 111 | * 发布第一个正式版本 1.0.0 112 | 113 | ## 0.0.3 [2021-08-17] 114 | * 新增 iOS IDFA 请求接口 115 | 116 | ## 0.0.2 [2021-08-16] 117 | * 新增 iOS 开屏广告 118 | 119 | ## 0.0.1 [2021-07-30] 120 | * 新增 Android 开屏广告,完成初始版本 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 FlutterAds 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Flutter QQ Ads

2 |

一款优质的优量汇(腾讯广告、广点通)Flutter 广告变现插件

3 |

♻️ 持续更新 ♻️

4 | 5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

20 | 21 | 22 |

🏆 FlutterAds ~ 致力于构建优质的 Flutter 变现插件

23 |
24 | 25 | ## 🚀 核心功能 26 | 27 | - ✅ 开屏广告 28 | - ✅ 插屏广告 29 | - ✅ 激励视频 30 | - ✅ Banner 31 | - ✅ 信息流 32 | - 🏆 二次激励(可有效提升收益)[🚀 Pro 版](https://flutterads.top/) 33 | - 🦥 预缓存(预加载,极速展示) [🎁 Pro 版](https://flutterads.top/) 34 | - 💰 实时价格 eCPM(收益统计、实时分佣)[🚀 Pro 版](https://flutterads.top/) 35 | - 🧪 测试助手(精准测试,稳定上线)[🚀 Pro 版](https://flutterads.top/) 36 | 37 | 38 | ## 📱 下载体验 39 | 40 | 41 | > 回复 `Pro` 进行体验 42 | 43 | ## 📣 推荐使用 GroMore 44 | 45 | - [🏆 Gromore Pro](https://flutterads.top/) 可进行多家广告瀑布流竞价,让您获得更高的广告收益,发挥最大的用户价值 46 | - [ 🎯 极速接入、快速体验、持续更新](https://github.com/FlutterAds/flutter_qq_ads/wiki) 47 | - [ 💰 变现套件 = 【GroMore】+【AdSpark】+【AdContent】](https://flutterads.top/) 48 | 49 | 50 | ## 📌 FlutterAds 广告系列插件 — 打造 Flutter 应用的变现新机会 51 | 52 | 致力于构建优质的 Flutter 变现插件,选择我们提供的高效广告插件,专为 Flutter 开发者量身定制!我们不仅涵盖国内各大广告平台,还支持国际市场,助你快速打开全球创收之门。 53 | 54 | |插件|描述| 55 | |-|-| 56 | |[🏆 FlutterAds](https://flutterads.top/)| 🎉 提供全方位的广告解决方案,轻松集成,变现效益翻倍 🚀,助力你的 Flutter 应用赢得更多收益!| 57 | |[🌐 flutter_gromore_ads](https://github.com/FlutterAds/flutter_gromore_ads)|字节跳动、穿山甲、GroMore 广告平台一站式聚合,助力你在 Flutter 中轻松集成多种广告源!| 58 | |[🌐 flutter_pangle_ads](https://github.com/FlutterAds/flutter_pangle_ads)|专为 Flutter 打造的字节跳动、穿山甲广告插件,让你畅享流量和收益!| 59 | |[🚢 flutter_pangle_global_ads](https://github.com/FlutterAds/flutter_pangle_global_ads)|面向全球用户的字节跳动、穿山甲国际版广告插件,全面支持海外市场的广告变现!| 60 | |[🌐 flutter_qq_ads](https://github.com/FlutterAds/flutter_qq_ads)| 集成腾讯广告、广点通、优量汇,快速为 Flutter 应用打开更多广告渠道!| 61 | |[Ⓜ flutter_ohos_ads](https://github.com/FlutterAds/flutter_ohos_ads)|专为 HarmonyOS(鸿蒙系统)优化的广告变现插件,提供专属 Flutter 广告支持!| 62 | |[📡 flutter_adspark](https://github.com/FlutterAds/flutter_adspark)|强大的广告监测、增长分析与事件管理功能,助力你提升广告投放效果,精准归因!| 63 | |[🎬 flutter_adcontent_pro](https://github.com/FlutterAds/flutter_adcontent)|支持短剧、小视频内容的穿山甲内容输出插件,助力你轻松打造短剧类应用!| 64 | |[📚 flutter_novel_story_pro](https://github.com/FlutterAds/flutter_novel_story)|支持短故事、小说等内容输出,带有内置阅读器和聚合首页,助力你轻松打造小说类应用!| 65 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.zero.flutter_qq_ads' 2 | version '1.0' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:7.3.1' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | mavenCentral() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | if (project.android.hasProperty("namespace")) { 26 | namespace = "com.zero.flutter_qq_ads" 27 | } 28 | compileSdk 33 29 | 30 | defaultConfig { 31 | minSdkVersion 19 32 | } 33 | lintOptions { 34 | disable 'InvalidPackage' 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation 'androidx.appcompat:appcompat:1.3.1' 40 | implementation 'com.google.android.material:material:1.3.0' 41 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 42 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 43 | // 广告 SDK https://central.sonatype.com/artifact/com.qq.e.union/union/versions 44 | implementation 'com.qq.e.union:union:4.620.1490' 45 | } 46 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_qq_ads' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 20 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/FlutterQqAdsPlugin.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import io.flutter.embedding.engine.plugins.FlutterPlugin; 6 | import io.flutter.embedding.engine.plugins.activity.ActivityAware; 7 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; 8 | import io.flutter.plugin.common.EventChannel; 9 | import io.flutter.plugin.common.MethodChannel; 10 | 11 | /** 12 | * FlutterQqAdsPlugin 13 | */ 14 | public class FlutterQqAdsPlugin implements FlutterPlugin, ActivityAware { 15 | // 方法通道 16 | private MethodChannel methodChannel; 17 | // 事件通道 18 | private EventChannel eventChannel; 19 | // 插件代理 20 | private PluginDelegate delegate; 21 | // 插件连接器 22 | private FlutterPluginBinding bind; 23 | 24 | @Override 25 | public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { 26 | bind = flutterPluginBinding; 27 | // 初始化方法通道和事件通道 28 | methodChannel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "flutter_qq_ads"); 29 | eventChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "flutter_qq_ads_event"); 30 | } 31 | 32 | 33 | @Override 34 | public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { 35 | // 解除方法通道和事件通道 36 | methodChannel.setMethodCallHandler(null); 37 | eventChannel.setStreamHandler(null); 38 | } 39 | 40 | 41 | @Override 42 | public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { 43 | this.delegate = new PluginDelegate(binding.getActivity(), bind); 44 | methodChannel.setMethodCallHandler(delegate); 45 | eventChannel.setStreamHandler(delegate); 46 | this.delegate.registerBannerView(); 47 | this.delegate.registerFeedView(); 48 | } 49 | 50 | @Override 51 | public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { 52 | onAttachedToActivity(binding); 53 | } 54 | 55 | 56 | @Override 57 | public void onDetachedFromActivityForConfigChanges() { 58 | onDetachedFromActivity(); 59 | } 60 | 61 | 62 | @Override 63 | public void onDetachedFromActivity() { 64 | this.delegate = null; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/event/AdErrorEvent.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.event; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * 广告错误事件 7 | */ 8 | public class AdErrorEvent extends AdEvent { 9 | // 错误码 10 | private final int errCode; 11 | // 错误信息 12 | private final String errMsg; 13 | // 错误事件实体 14 | public AdErrorEvent(String adId, int errCode, String errMsg) { 15 | super(adId, AdEventAction.onAdError); 16 | this.errCode = errCode; 17 | this.errMsg = errMsg; 18 | } 19 | 20 | /** 21 | * 重写 toMap 方法 22 | * @return 返回错误事件的map 23 | */ 24 | @Override 25 | public HashMap toMap() { 26 | HashMap newMap= super.toMap(); 27 | newMap.put("errCode",errCode); 28 | newMap.put("errMsg",errMsg); 29 | return newMap; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/event/AdEvent.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.event; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * 广告事件 7 | */ 8 | public class AdEvent { 9 | // 广告 id 10 | private final String adId; 11 | // 操作 12 | private final String action; 13 | 14 | public AdEvent(String adId, String action) { 15 | this.adId = adId; 16 | this.action = action; 17 | } 18 | 19 | /** 20 | * 转换为 Map 方面传输 21 | * @return 转换后的 Map 对象 22 | */ 23 | public HashMap toMap() { 24 | HashMap map=new HashMap(); 25 | map.put("adId",adId); 26 | map.put("action",action); 27 | return map; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/event/AdEventAction.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.event; 2 | 3 | /** 4 | * 广告事件操作 5 | */ 6 | public class AdEventAction { 7 | // 广告错误 8 | public static final String onAdError="onAdError"; 9 | // 广告加载成功 10 | public static final String onAdLoaded="onAdLoaded"; 11 | // 广告填充 12 | public static final String onAdPresent="onAdPresent"; 13 | // 广告曝光 14 | public static final String onAdExposure="onAdExposure"; 15 | // 广告关闭 16 | public static final String onAdClosed="onAdClosed"; 17 | // 广告点击 18 | public static final String onAdClicked="onAdClicked"; 19 | // 广告跳过 20 | public static final String onAdSkip="onAdSkip"; 21 | // 广告播放或计时完毕 22 | public static final String onAdComplete="onAdComplete"; 23 | // 获得广告激励 24 | public static final String onAdReward="onAdReward"; 25 | } 26 | -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/event/AdEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.event; 2 | 3 | import com.zero.flutter_qq_ads.PluginDelegate; 4 | 5 | /** 6 | * 广告事件处理类 7 | */ 8 | public class AdEventHandler { 9 | // 广告事件处理对象 10 | private static volatile AdEventHandler _instance; 11 | 12 | /** 13 | * 获取广告事件处理类 14 | * 15 | * @return 广告事件处理对象 16 | */ 17 | public static AdEventHandler getInstance() { 18 | if (_instance == null) { 19 | synchronized (AdEventHandler.class) { 20 | _instance = new AdEventHandler(); 21 | } 22 | } 23 | return _instance; 24 | } 25 | 26 | /** 27 | * 添加广告事件 28 | * 29 | * @param event 广告事件 30 | */ 31 | public void sendEvent(AdEvent event) { 32 | if (event != null) { 33 | PluginDelegate.getInstance().addEvent(event.toMap()); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/event/AdRewardEvent.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.event; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * 广告激励事件 7 | */ 8 | public class AdRewardEvent extends AdEvent { 9 | // 服务端验证唯一id 10 | private final String transId; 11 | // 服务端验证的自定义信息 12 | private final String customData; 13 | // 服务端验证的用户信息 14 | private final String userId; 15 | 16 | public AdRewardEvent(String adId, String action, String transId, String customData, String userId) { 17 | super(adId, AdEventAction.onAdReward); 18 | this.transId = transId; 19 | this.customData = customData; 20 | this.userId = userId; 21 | } 22 | 23 | @Override 24 | public HashMap toMap() { 25 | HashMap newMap = super.toMap(); 26 | newMap.put("transId", transId); 27 | newMap.put("customData", customData); 28 | newMap.put("userId", userId); 29 | return newMap; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/load/FeedAdLoad.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.load; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.util.Log; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 9 | 10 | import com.qq.e.ads.nativ.ADSize; 11 | import com.qq.e.ads.nativ.NativeExpressAD; 12 | import com.qq.e.ads.nativ.NativeExpressADView; 13 | import com.qq.e.comm.util.AdError; 14 | import com.zero.flutter_qq_ads.PluginDelegate; 15 | import com.zero.flutter_qq_ads.event.AdEventAction; 16 | import com.zero.flutter_qq_ads.page.BaseAdPage; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Locale; 21 | 22 | import io.flutter.plugin.common.MethodCall; 23 | import io.flutter.plugin.common.MethodChannel; 24 | 25 | /** 26 | * 信息流加载对象 27 | */ 28 | public class FeedAdLoad extends BaseAdPage implements NativeExpressAD.NativeExpressADListener { 29 | private final String TAG = FeedAdLoad.class.getSimpleName(); 30 | private MethodChannel.Result result; 31 | 32 | /** 33 | * 加载信息流广告列表 34 | * 35 | * @param call 36 | * @param result 37 | */ 38 | public void loadFeedAdList(Activity activity, @NonNull MethodCall call, @NonNull MethodChannel.Result result) { 39 | this.result = result; 40 | showAd(activity, call); 41 | } 42 | 43 | @Override 44 | public void loadAd(@NonNull MethodCall call) { 45 | // 获取请求模板广告素材的尺寸 46 | int width = call.argument("width"); 47 | int height = call.argument("height"); 48 | int count = call.argument("count"); 49 | NativeExpressAD ad = new NativeExpressAD(activity, new ADSize(width, height), this.posId, this); 50 | ad.loadAD(count); 51 | } 52 | 53 | @Override 54 | public void onADLoaded(List list) { 55 | Log.i(TAG, "onADLoaded"); 56 | List adResultList = new ArrayList<>(); 57 | if (list == null || list.size() == 0) { 58 | this.result.success(adResultList); 59 | return; 60 | } 61 | for (NativeExpressADView adItem : list) { 62 | int key = adItem.hashCode(); 63 | adResultList.add(key); 64 | FeedAdManager.getInstance().putAd(key, adItem); 65 | } 66 | // 添加广告事件 67 | sendEvent(AdEventAction.onAdLoaded); 68 | this.result.success(adResultList); 69 | } 70 | 71 | @Override 72 | public void onRenderFail(NativeExpressADView nativeExpressADView) { 73 | Log.i(TAG, "onRenderFail"); 74 | sendErrorEvent(-100, "onRenderFail"); 75 | sendBroadcastEvent(nativeExpressADView, AdEventAction.onAdError); 76 | } 77 | 78 | @Override 79 | public void onRenderSuccess(NativeExpressADView nativeExpressADView) { 80 | Log.i(TAG, "onRenderSuccess"); 81 | sendEvent(AdEventAction.onAdPresent); 82 | sendBroadcastEvent(nativeExpressADView, AdEventAction.onAdPresent); 83 | } 84 | 85 | @Override 86 | public void onADExposure(NativeExpressADView nativeExpressADView) { 87 | Log.i(TAG, "onADExposure"); 88 | sendEvent(AdEventAction.onAdExposure); 89 | } 90 | 91 | @Override 92 | public void onADClicked(NativeExpressADView nativeExpressADView) { 93 | Log.i(TAG, "onADClicked"); 94 | sendEvent(AdEventAction.onAdClicked); 95 | } 96 | 97 | @Override 98 | public void onADClosed(NativeExpressADView nativeExpressADView) { 99 | Log.i(TAG, "onADClosed"); 100 | sendEvent(AdEventAction.onAdClosed); 101 | sendBroadcastEvent(nativeExpressADView, AdEventAction.onAdClosed); 102 | } 103 | 104 | private void sendBroadcastEvent(NativeExpressADView adView, String event) { 105 | Intent intent = new Intent(); 106 | intent.setAction(PluginDelegate.KEY_FEED_VIEW + "_" + adView.hashCode()); 107 | intent.putExtra("event", event); 108 | boolean result = LocalBroadcastManager.getInstance(activity).sendBroadcast(intent); 109 | } 110 | 111 | @Override 112 | public void onADLeftApplication(NativeExpressADView nativeExpressADView) { 113 | 114 | } 115 | 116 | @Override 117 | public void onNoAD(AdError error) { 118 | String msg = String.format(Locale.getDefault(), "onError, error code: %d, error msg: %s", 119 | error.getErrorCode(), error.getErrorMsg()); 120 | Log.i(TAG, "onError, adError=" + msg); 121 | sendErrorEvent(error.getErrorCode(), error.getErrorMsg()); 122 | this.result.success(new ArrayList()); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/load/FeedAdManager.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.load; 2 | 3 | import com.qq.e.ads.nativ.NativeExpressADView; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * 信息流广告管理 10 | */ 11 | public class FeedAdManager { 12 | private final String TAG = FeedAdManager.class.getSimpleName(); 13 | // 信息流广告管理类对象 14 | private static FeedAdManager _instance; 15 | 16 | public static synchronized FeedAdManager getInstance() { 17 | if (_instance == null) { 18 | synchronized (FeedAdManager.class) { 19 | _instance = new FeedAdManager(); 20 | } 21 | } 22 | return _instance; 23 | } 24 | 25 | // 信息流广告列表 26 | private final Map feedAdList = new HashMap(); 27 | 28 | /** 29 | * 添加广告渲染对象 30 | * 31 | * @param key 广告缓存id 32 | * @param ad 广告渲染对象 33 | */ 34 | public void putAd(int key, NativeExpressADView ad) { 35 | feedAdList.put(key, ad); 36 | } 37 | 38 | /** 39 | * 获取广告渲染对象 40 | * 41 | * @param key 广告缓存id 42 | * @return 广告渲染对象 43 | */ 44 | public NativeExpressADView getAd(int key) { 45 | return feedAdList.get(key); 46 | } 47 | 48 | /** 49 | * 删除广告渲染对象 50 | * 51 | * @param key 广告缓存id 52 | * @return 广告渲染对象 53 | */ 54 | public NativeExpressADView removeAd(int key) { 55 | return feedAdList.remove(key); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/page/AdBannerView.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.page; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import android.view.View; 6 | import android.widget.FrameLayout; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | 11 | import com.qq.e.ads.banner2.UnifiedBannerADListener; 12 | import com.qq.e.ads.banner2.UnifiedBannerView; 13 | import com.qq.e.comm.util.AdError; 14 | import com.zero.flutter_qq_ads.PluginDelegate; 15 | import com.zero.flutter_qq_ads.event.AdEventAction; 16 | 17 | import io.flutter.plugin.common.MethodCall; 18 | import io.flutter.plugin.platform.PlatformView; 19 | 20 | import java.util.Locale; 21 | import java.util.Map; 22 | 23 | /** 24 | * Banner 广告 View 25 | */ 26 | class AdBannerView extends BaseAdPage implements PlatformView, UnifiedBannerADListener { 27 | private final String TAG = AdBannerView.class.getSimpleName(); 28 | @NonNull 29 | private final FrameLayout frameLayout; 30 | private final PluginDelegate pluginDelegate; 31 | private int id; 32 | private UnifiedBannerView bv; 33 | private Map params; 34 | 35 | 36 | AdBannerView(@NonNull Context context, int id, @Nullable Map creationParams, PluginDelegate pluginDelegate) { 37 | this.id = id; 38 | this.pluginDelegate = pluginDelegate; 39 | this.params = creationParams; 40 | frameLayout = new FrameLayout(context); 41 | MethodCall call = new MethodCall("AdBannerView", creationParams); 42 | showAd(this.pluginDelegate.activity,call); 43 | } 44 | 45 | @NonNull 46 | @Override 47 | public View getView() { 48 | return frameLayout; 49 | } 50 | 51 | @Override 52 | public void dispose() { 53 | disposeAd(); 54 | } 55 | 56 | @Override 57 | public void loadAd( @NonNull MethodCall call) { 58 | // 获取轮播时间间隔参数 59 | int interval= (int) this.params.get("interval"); 60 | // 加载广告 Banner 61 | bv = new UnifiedBannerView(activity, posId, this); 62 | frameLayout.addView(bv); 63 | // 设置轮播时间间隔 64 | bv.setRefresh(interval); 65 | bv.loadAD(); 66 | } 67 | 68 | /** 69 | * 销毁广告 70 | */ 71 | private void disposeAd(){ 72 | frameLayout.removeAllViews(); 73 | if (bv != null) { 74 | bv.destroy(); 75 | } 76 | } 77 | 78 | @Override 79 | public void onNoAD(AdError error) { 80 | String msg = String.format(Locale.getDefault(), "onNoAD, error code: %d, error msg: %s", 81 | error.getErrorCode(), error.getErrorMsg()); 82 | Log.e(TAG, msg); 83 | sendErrorEvent(error.getErrorCode(), error.getErrorMsg()); 84 | disposeAd(); 85 | } 86 | 87 | @Override 88 | public void onADReceive() { 89 | Log.i(TAG, "onADReceive"); 90 | sendEvent(AdEventAction.onAdLoaded); 91 | } 92 | 93 | @Override 94 | public void onADExposure() { 95 | Log.i(TAG, "onADExposure"); 96 | sendEvent(AdEventAction.onAdExposure); 97 | } 98 | 99 | @Override 100 | public void onADClosed() { 101 | Log.i(TAG, "onADClosed"); 102 | sendEvent(AdEventAction.onAdClosed); 103 | disposeAd(); 104 | } 105 | 106 | @Override 107 | public void onADClicked() { 108 | Log.i(TAG, "onADClicked"); 109 | sendEvent(AdEventAction.onAdClicked); 110 | } 111 | 112 | @Override 113 | public void onADLeftApplication() { 114 | Log.i(TAG, "onADLeftApplication"); 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/page/AdFeedView.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.page; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.view.View; 8 | import android.widget.FrameLayout; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 13 | 14 | import com.qq.e.ads.nativ.NativeExpressADView; 15 | import com.zero.flutter_qq_ads.PluginDelegate; 16 | import com.zero.flutter_qq_ads.event.AdEventAction; 17 | import com.zero.flutter_qq_ads.load.FeedAdManager; 18 | 19 | import java.util.Map; 20 | 21 | import io.flutter.plugin.common.MethodCall; 22 | import io.flutter.plugin.common.MethodChannel; 23 | import io.flutter.plugin.platform.PlatformView; 24 | 25 | /** 26 | * Feed 信息流广告 View 27 | */ 28 | class AdFeedView extends BaseAdPage implements PlatformView { 29 | private final String TAG = AdFeedView.class.getSimpleName(); 30 | @NonNull 31 | private final FrameLayout frameLayout; 32 | private final PluginDelegate pluginDelegate; 33 | private int id; 34 | private NativeExpressADView fad; 35 | private BroadcastReceiver receiver; 36 | 37 | 38 | AdFeedView(@NonNull Context context, int id, @Nullable Map creationParams, PluginDelegate pluginDelegate) { 39 | this.id = id; 40 | this.pluginDelegate = pluginDelegate; 41 | frameLayout = new FrameLayout(context); 42 | MethodCall call = new MethodCall("AdFeedView", creationParams); 43 | showAd(this.pluginDelegate.activity, call); 44 | } 45 | 46 | @NonNull 47 | @Override 48 | public View getView() { 49 | return frameLayout; 50 | } 51 | 52 | @Override 53 | public void dispose() { 54 | removeAd(); 55 | } 56 | 57 | @Override 58 | public void loadAd(@NonNull MethodCall call) { 59 | int key = Integer.parseInt(this.posId); 60 | regReceiver(key); 61 | fad = FeedAdManager.getInstance().getAd(key); 62 | if (fad != null) { 63 | if (frameLayout.getChildCount() > 0) { 64 | frameLayout.removeAllViews(); 65 | } 66 | fad.render(); 67 | frameLayout.addView(fad); 68 | } 69 | } 70 | 71 | /** 72 | * 注册广播 73 | * 74 | * @param key key 75 | */ 76 | private void regReceiver(int key) { 77 | // 注册广播 78 | receiver = new BroadcastReceiver() { 79 | @Override 80 | public void onReceive(Context context, Intent intent) { 81 | String event = intent.getStringExtra("event"); 82 | if (AdEventAction.onAdClosed.equals(event) || AdEventAction.onAdError.equals(event)) { 83 | AdFeedView.this.disposeAd(); 84 | } 85 | } 86 | }; 87 | IntentFilter intentFilter = new IntentFilter(PluginDelegate.KEY_FEED_VIEW + "_" + key); 88 | LocalBroadcastManager.getInstance(activity).registerReceiver(receiver, intentFilter); 89 | } 90 | 91 | /** 92 | * 移除广告 93 | */ 94 | private void removeAd() { 95 | frameLayout.removeAllViews(); 96 | // 注销广播 97 | if (receiver != null) { 98 | LocalBroadcastManager.getInstance(activity).unregisterReceiver(receiver); 99 | } 100 | } 101 | 102 | /** 103 | * 销毁广告 104 | */ 105 | private void disposeAd() { 106 | removeAd(); 107 | FeedAdManager.getInstance().removeAd(Integer.parseInt(this.posId)); 108 | if (fad != null) { 109 | fad.destroy(); 110 | } 111 | } 112 | 113 | } -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/page/AdSplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.page; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | import androidx.appcompat.widget.AppCompatImageView; 5 | 6 | import android.os.Bundle; 7 | import android.text.TextUtils; 8 | import android.util.Log; 9 | import android.view.KeyEvent; 10 | import android.view.View; 11 | import android.widget.FrameLayout; 12 | 13 | import com.qq.e.ads.splash.SplashAD; 14 | import com.qq.e.ads.splash.SplashADListener; 15 | import com.qq.e.comm.util.AdError; 16 | import com.zero.flutter_qq_ads.PluginDelegate; 17 | import com.zero.flutter_qq_ads.R; 18 | import com.zero.flutter_qq_ads.event.AdErrorEvent; 19 | import com.zero.flutter_qq_ads.event.AdEvent; 20 | import com.zero.flutter_qq_ads.event.AdEventAction; 21 | import com.zero.flutter_qq_ads.event.AdEventHandler; 22 | 23 | /** 24 | * 开屏广告 25 | */ 26 | public class AdSplashActivity extends AppCompatActivity implements SplashADListener { 27 | private final String TAG = AdSplashActivity.class.getSimpleName(); 28 | // 广告容器 29 | private FrameLayout ad_container; 30 | // 自定义品牌 logo 31 | private AppCompatImageView ad_logo; 32 | // 广告位 id 33 | private String posId; 34 | // 是否全屏 35 | private boolean isFullScreen; 36 | // 开屏广告 37 | private SplashAD splashAD; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_ad_splash); 43 | initView(); 44 | initData(); 45 | } 46 | 47 | /** 48 | * 初始化View 49 | */ 50 | private void initView() { 51 | ad_container = findViewById(R.id.splash_ad_container); 52 | ad_logo = findViewById(R.id.splash_ad_logo); 53 | } 54 | 55 | /** 56 | * 初始化数据 57 | */ 58 | private void initData() { 59 | // 获取参数 60 | posId = getIntent().getStringExtra(PluginDelegate.KEY_POSID); 61 | String logo = getIntent().getStringExtra(PluginDelegate.KEY_LOGO); 62 | double fetchDelay = getIntent().getDoubleExtra(PluginDelegate.KEY_FETCH_DELAY, 0); 63 | int absFetchDelay = (int) (fetchDelay * 1000); 64 | isFullScreen = TextUtils.isEmpty(logo); 65 | // 创建开屏广告 66 | splashAD = new SplashAD(this, posId, this, absFetchDelay); 67 | if (isFullScreen) { 68 | // logo 为空则加载全屏广告 69 | ad_logo.setVisibility(View.GONE); 70 | splashAD.fetchFullScreenAdOnly(); 71 | } else { 72 | // 加载 logo 73 | int resId = getMipmapId(logo); 74 | if (resId > 0) { 75 | ad_logo.setVisibility(View.VISIBLE); 76 | ad_logo.setImageResource(resId); 77 | } 78 | // 加载广告 79 | splashAD.fetchAdOnly(); 80 | } 81 | } 82 | 83 | @Override 84 | public void onADDismissed() { 85 | Log.d(TAG, "onADDismissed"); 86 | finishPage(); 87 | AdEventHandler.getInstance().sendEvent(new AdEvent(this.posId, AdEventAction.onAdClosed)); 88 | } 89 | 90 | @Override 91 | public void onNoAD(AdError adError) { 92 | Log.d(TAG, "onNoAD adError:" + adError.getErrorMsg()); 93 | finishPage(); 94 | AdEventHandler.getInstance().sendEvent(new AdErrorEvent(this.posId, adError.getErrorCode(), adError.getErrorMsg())); 95 | } 96 | 97 | @Override 98 | public void onADPresent() { 99 | Log.d(TAG, "onADPresent"); 100 | AdEventHandler.getInstance().sendEvent(new AdEvent(this.posId, AdEventAction.onAdPresent)); 101 | } 102 | 103 | @Override 104 | public void onADClicked() { 105 | Log.d(TAG, "onADClicked"); 106 | AdEventHandler.getInstance().sendEvent(new AdEvent(this.posId, AdEventAction.onAdClicked)); 107 | } 108 | 109 | @Override 110 | public void onADTick(long millisUntilFinished) { 111 | Log.d(TAG, "onADTick millisUntilFinished:" + millisUntilFinished); 112 | } 113 | 114 | @Override 115 | public void onADExposure() { 116 | Log.d(TAG, "onADExposure"); 117 | AdEventHandler.getInstance().sendEvent(new AdEvent(this.posId, AdEventAction.onAdExposure)); 118 | } 119 | 120 | @Override 121 | public void onADLoaded(long expireTimestamp) { 122 | Log.d(TAG, "onADLoaded expireTimestamp:" + expireTimestamp); 123 | AdEventHandler.getInstance().sendEvent(new AdEvent(this.posId, AdEventAction.onAdLoaded)); 124 | if (isFullScreen) { 125 | splashAD.showFullScreenAd(ad_container); 126 | } else { 127 | splashAD.showAd(ad_container); 128 | } 129 | 130 | } 131 | 132 | /** 133 | * 完成广告,退出开屏页面 134 | */ 135 | private void finishPage() { 136 | finish(); 137 | // 设置退出动画 138 | overridePendingTransition(0, android.R.anim.fade_out); 139 | } 140 | 141 | /** 142 | * 开屏页一定要禁止用户对返回按钮的控制,否则将可能导致用户手动退出了App而广告无法正常曝光和计费 143 | */ 144 | @Override 145 | public boolean onKeyDown(int keyCode, KeyEvent event) { 146 | if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME) { 147 | return true; 148 | } 149 | return super.onKeyDown(keyCode, event); 150 | } 151 | 152 | /** 153 | * 获取图片资源的id 154 | * 155 | * @param resName 资源名称,不带后缀 156 | * @return 返回资源id 157 | */ 158 | private int getMipmapId(String resName) { 159 | return getResources().getIdentifier(resName, "mipmap", getPackageName()); 160 | } 161 | } -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/page/BaseAdPage.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.page; 2 | 3 | import static com.zero.flutter_qq_ads.PluginDelegate.KEY_POSID; 4 | 5 | import android.app.Activity; 6 | 7 | import com.zero.flutter_qq_ads.event.AdErrorEvent; 8 | import com.zero.flutter_qq_ads.event.AdEvent; 9 | import com.zero.flutter_qq_ads.event.AdEventHandler; 10 | 11 | import io.flutter.plugin.common.MethodCall; 12 | 13 | /** 14 | * 基础广告页面 15 | */ 16 | public abstract class BaseAdPage { 17 | // 上下文 18 | protected Activity activity; 19 | // 广告位 id 20 | protected String posId; 21 | 22 | 23 | /** 24 | * 显示广告 25 | * 26 | * @param activity 上下文 27 | * @param call 方法调用 28 | */ 29 | public void showAd(Activity activity, MethodCall call) { 30 | this.activity = activity; 31 | this.posId = call.argument(KEY_POSID); 32 | loadAd(call); 33 | } 34 | 35 | /** 36 | * 加载广告 37 | * 38 | * @param call 方法调用 39 | */ 40 | public abstract void loadAd(MethodCall call); 41 | 42 | 43 | /** 44 | * 发送广告事件 45 | * 46 | * @param event 广告事件 47 | */ 48 | protected void sendEvent(AdEvent event) { 49 | AdEventHandler.getInstance().sendEvent(event); 50 | } 51 | 52 | /** 53 | * 发送广告事件 54 | * 55 | * @param action 操作 56 | */ 57 | protected void sendEvent(String action) { 58 | sendEvent(new AdEvent(posId, action)); 59 | } 60 | 61 | /** 62 | * 发送错误事件 63 | * 64 | * @param errCode 错误码 65 | * @param errMsg 错误事件 66 | */ 67 | protected void sendErrorEvent(int errCode, String errMsg) { 68 | sendEvent(new AdErrorEvent(posId, errCode, errMsg)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/page/InterstitialPage.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.page; 2 | 3 | import android.app.Activity; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.qq.e.ads.cfg.VideoOption; 9 | import com.qq.e.ads.interstitial2.ADRewardListener; 10 | import com.qq.e.ads.interstitial2.UnifiedInterstitialAD; 11 | import com.qq.e.ads.interstitial2.UnifiedInterstitialADListener; 12 | import com.qq.e.ads.rewardvideo.RewardVideoAD; 13 | import com.qq.e.ads.rewardvideo.ServerSideVerificationOptions; 14 | import com.qq.e.comm.util.AdError; 15 | import com.zero.flutter_qq_ads.event.AdErrorEvent; 16 | import com.zero.flutter_qq_ads.event.AdEvent; 17 | import com.zero.flutter_qq_ads.event.AdEventAction; 18 | import com.zero.flutter_qq_ads.event.AdEventHandler; 19 | import com.zero.flutter_qq_ads.event.AdRewardEvent; 20 | 21 | import java.util.Locale; 22 | import java.util.Map; 23 | 24 | import io.flutter.plugin.common.MethodCall; 25 | 26 | /** 27 | * 插屏广告 28 | */ 29 | public class InterstitialPage extends BaseAdPage implements UnifiedInterstitialADListener, ADRewardListener { 30 | private final String TAG = InterstitialPage.class.getSimpleName(); 31 | // 插屏广告 32 | private UnifiedInterstitialAD iad; 33 | // popup 形式展示 34 | private boolean showPopup = false; 35 | // 全屏视频形式展示 36 | private boolean showFullScreenVideo = false; 37 | // 激励视频形式展示 38 | private boolean showRewardVideo = false; 39 | // 设置激励视频服务端验证的自定义信息 40 | private String customData; 41 | // 设置服务端验证的用户信息 42 | private String userId; 43 | 44 | @Override 45 | public void loadAd(@NonNull MethodCall call) { 46 | showPopup = call.argument("showPopup"); 47 | showFullScreenVideo = call.argument("showFullScreenVideo"); 48 | showRewardVideo = call.argument("showRewardVideo"); 49 | iad = new UnifiedInterstitialAD(activity, posId, this); 50 | setVideoOption(call); 51 | // 插屏全屏视频或插屏激励视频加载方式 52 | if (showFullScreenVideo || showRewardVideo) { 53 | iad.loadFullScreenAD(); 54 | } else { 55 | iad.loadAD(); 56 | } 57 | 58 | } 59 | 60 | 61 | /** 62 | * 设置视频参数 63 | */ 64 | private void setVideoOption(@NonNull MethodCall call) { 65 | boolean autoPlayOnWifi = call.argument("autoPlayOnWifi"); 66 | boolean autoPlayMuted = call.argument("autoPlayMuted"); 67 | VideoOption.Builder builder = new VideoOption.Builder(); 68 | VideoOption option = builder 69 | .setAutoPlayPolicy(autoPlayOnWifi ? 0 : 1) 70 | .setAutoPlayMuted(autoPlayMuted) 71 | .build(); 72 | iad.setVideoOption(option); 73 | // 如果是激励视频,则设置信息 74 | if (showRewardVideo) { 75 | // 设置激励监听 76 | iad.setRewardListener(this); 77 | customData = call.argument("customData"); 78 | userId = call.argument("userId"); 79 | // 设置服务端验证信息 80 | ServerSideVerificationOptions options = new ServerSideVerificationOptions.Builder() 81 | .setCustomData(customData) 82 | .setUserId(userId) // 设置服务端验证的用户信息 83 | .build(); 84 | iad.setServerSideVerificationOptions(options); 85 | } 86 | 87 | } 88 | 89 | /** 90 | * 显示广告 91 | */ 92 | private void show() { 93 | if (iad != null && iad.isValid()) { 94 | iad.show(); 95 | } else { 96 | Log.d(TAG, "请加载广告并渲染成功后再进行展示 ! "); 97 | } 98 | } 99 | 100 | /** 101 | * 以 Popup 形式显示广告 102 | */ 103 | private void showAsPopup() { 104 | if (iad != null && iad.isValid()) { 105 | iad.show(); 106 | } else { 107 | Log.d(TAG, "请加载广告并渲染成功后再进行展示 ! "); 108 | } 109 | } 110 | 111 | /** 112 | * 显示全屏视频广告 113 | */ 114 | private void showAsFullScreen() { 115 | if (iad != null && iad.isValid()) { 116 | iad.showFullScreenAD(activity); 117 | } else { 118 | Log.d(TAG, "请加载广告并渲染成功后再进行展示 ! "); 119 | } 120 | } 121 | 122 | /** 123 | * 全屏视频形式展示 124 | */ 125 | public boolean isShowFullScreenVideo() { 126 | return showFullScreenVideo; 127 | } 128 | 129 | /** 130 | * 设置全屏视频形式展示 131 | * 132 | * @param showFullScreenVideo 是否展示全屏视屏 133 | */ 134 | public void setShowFullScreenVideo(boolean showFullScreenVideo) { 135 | this.showFullScreenVideo = showFullScreenVideo; 136 | } 137 | 138 | @Override 139 | public void onADReceive() { 140 | Log.d(TAG, "onADReceive eCPMLevel = " + iad.getECPMLevel() + ", ECPM: " + iad.getECPM() + ", videoduration=" + iad.getVideoDuration()); 141 | sendEvent(AdEventAction.onAdLoaded); 142 | } 143 | 144 | @Override 145 | public void onVideoCached() { 146 | Log.i(TAG, "onVideoCached"); 147 | } 148 | 149 | @Override 150 | public void onNoAD(AdError error) { 151 | String msg = String.format(Locale.getDefault(), "onNoAD, error code: %d, error msg: %s", 152 | error.getErrorCode(), error.getErrorMsg()); 153 | Log.e(TAG, msg); 154 | sendErrorEvent(error.getErrorCode(), error.getErrorMsg()); 155 | } 156 | 157 | @Override 158 | public void onADOpened() { 159 | Log.i(TAG, "onADOpened"); 160 | } 161 | 162 | @Override 163 | public void onADExposure() { 164 | Log.i(TAG, "onADExposure"); 165 | sendEvent(AdEventAction.onAdExposure); 166 | } 167 | 168 | @Override 169 | public void onADClicked() { 170 | Log.i(TAG, "onADClicked"); 171 | sendEvent(AdEventAction.onAdClicked); 172 | } 173 | 174 | @Override 175 | public void onADLeftApplication() { 176 | Log.i(TAG, "onADLeftApplication"); 177 | } 178 | 179 | @Override 180 | public void onADClosed() { 181 | Log.i(TAG, "onADClosed"); 182 | sendEvent(AdEventAction.onAdClosed); 183 | } 184 | 185 | @Override 186 | public void onRenderSuccess() { 187 | Log.i(TAG, "onRenderSuccess,建议在此回调后再调用展示方法"); 188 | if (showFullScreenVideo || showRewardVideo) { 189 | showAsFullScreen(); 190 | } else { 191 | if (showPopup) { 192 | showAsPopup(); 193 | } else { 194 | show(); 195 | } 196 | } 197 | } 198 | 199 | @Override 200 | public void onRenderFail() { 201 | Log.i(TAG, "onRenderFail"); 202 | sendErrorEvent(-100, "onRenderFail"); 203 | 204 | } 205 | 206 | @Override 207 | public void onReward(Map map) { 208 | String transId = (String) map.get(ServerSideVerificationOptions.TRANS_ID); 209 | Log.i(TAG, "onReward " + transId); // 获取服务端验证的唯一 ID 210 | sendEvent(new AdRewardEvent(this.posId, AdEventAction.onAdReward, transId, customData, userId)); 211 | } 212 | } -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/page/NativeViewFactory.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.page; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.Nullable; 6 | import androidx.annotation.NonNull; 7 | 8 | import com.zero.flutter_qq_ads.PluginDelegate; 9 | 10 | import io.flutter.plugin.common.StandardMessageCodec; 11 | import io.flutter.plugin.platform.PlatformView; 12 | import io.flutter.plugin.platform.PlatformViewFactory; 13 | 14 | import java.util.Map; 15 | 16 | /** 17 | * 原生平台 View 工厂 18 | */ 19 | public class NativeViewFactory extends PlatformViewFactory { 20 | @NonNull 21 | private final String viewName;// View 名字 22 | private final PluginDelegate pluginDelegate; // 插件代理类 23 | 24 | public NativeViewFactory(String viewName, @NonNull PluginDelegate pluginDelegate) { 25 | super(StandardMessageCodec.INSTANCE); 26 | this.viewName = viewName; 27 | this.pluginDelegate = pluginDelegate; 28 | } 29 | 30 | @NonNull 31 | @Override 32 | public PlatformView create(@NonNull Context context, int id, @Nullable Object args) { 33 | final Map creationParams = (Map) args; 34 | if (this.viewName.equals(PluginDelegate.KEY_BANNER_VIEW)) { 35 | return new AdBannerView(context, id, creationParams, pluginDelegate); 36 | } else { 37 | return new AdFeedView(context, id, creationParams, pluginDelegate); 38 | } 39 | 40 | 41 | } 42 | } -------------------------------------------------------------------------------- /android/src/main/java/com/zero/flutter_qq_ads/page/RewardVideoPage.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads.page; 2 | 3 | import android.app.Activity; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.qq.e.ads.rewardvideo.RewardVideoAD; 9 | import com.qq.e.ads.rewardvideo.RewardVideoADListener; 10 | import com.qq.e.ads.rewardvideo.ServerSideVerificationOptions; 11 | import com.qq.e.comm.util.AdError; 12 | import com.zero.flutter_qq_ads.event.AdErrorEvent; 13 | import com.zero.flutter_qq_ads.event.AdEvent; 14 | import com.zero.flutter_qq_ads.event.AdEventAction; 15 | import com.zero.flutter_qq_ads.event.AdEventHandler; 16 | import com.zero.flutter_qq_ads.event.AdRewardEvent; 17 | 18 | import java.util.Locale; 19 | import java.util.Map; 20 | 21 | import io.flutter.plugin.common.MethodCall; 22 | 23 | /** 24 | * 激励视频页面 25 | */ 26 | public class RewardVideoPage extends BaseAdPage implements RewardVideoADListener { 27 | private static final String TAG = RewardVideoPage.class.getSimpleName(); 28 | private RewardVideoAD rvad; 29 | // 设置激励视频服务端验证的自定义信息 30 | private String customData; 31 | // 设置服务端验证的用户信息 32 | private String userId; 33 | 34 | @Override 35 | public void loadAd(@NonNull MethodCall call) { 36 | boolean playMuted = call.argument("playMuted"); 37 | customData = call.argument("customData"); 38 | userId = call.argument("userId"); 39 | // 1. 初始化激励视频广告 40 | rvad = new RewardVideoAD(activity, posId, this, !playMuted); 41 | // 设置服务端验证信息 42 | ServerSideVerificationOptions options = new ServerSideVerificationOptions.Builder() 43 | .setCustomData(customData) 44 | .setUserId(userId) // 设置服务端验证的用户信息 45 | .build(); 46 | rvad.setServerSideVerificationOptions(options); 47 | // 2. 加载激励视频广告 48 | rvad.loadAD(); 49 | } 50 | 51 | 52 | /** 53 | * 广告加载成功,可在此回调后进行广告展示 54 | **/ 55 | @Override 56 | public void onADLoad() { 57 | Log.i(TAG, "onADLoad"); 58 | if (rvad != null) { 59 | rvad.showAD(); 60 | } 61 | sendEvent(AdEventAction.onAdLoaded); 62 | } 63 | 64 | /** 65 | * 视频素材缓存成功,可在此回调后进行广告展示 66 | */ 67 | @Override 68 | public void onVideoCached() { 69 | Log.i(TAG, "onVideoCached"); 70 | } 71 | 72 | /** 73 | * 激励视频广告页面展示 74 | */ 75 | @Override 76 | public void onADShow() { 77 | Log.i(TAG, "onADShow"); 78 | sendEvent(AdEventAction.onAdPresent); 79 | } 80 | 81 | /** 82 | * 激励视频广告曝光 83 | */ 84 | @Override 85 | public void onADExpose() { 86 | Log.i(TAG, "onADExpose"); 87 | sendEvent(AdEventAction.onAdExposure); 88 | } 89 | 90 | /** 91 | * 激励视频触发激励(观看视频大于一定时长或者视频播放完毕) 92 | * 93 | * @param map 若选择了服务端验证,可以通过 ServerSideVerificationOptions#TRANS_ID 键从 map 中获取此次交易的 id;若未选择服务端验证,则不需关注 map 参数。 94 | */ 95 | @Override 96 | public void onReward(Map map) { 97 | String transId = (String) map.get(ServerSideVerificationOptions.TRANS_ID); 98 | Log.i(TAG, "onReward " + transId); // 获取服务端验证的唯一 ID 99 | sendEvent(new AdRewardEvent(this.posId, AdEventAction.onAdReward, transId, customData, userId)); 100 | } 101 | 102 | /** 103 | * 激励视频广告被点击 104 | */ 105 | @Override 106 | public void onADClick() { 107 | Log.i(TAG, "onADClick"); 108 | sendEvent(AdEventAction.onAdClicked); 109 | } 110 | 111 | /** 112 | * 激励视频播放完毕 113 | */ 114 | @Override 115 | public void onVideoComplete() { 116 | Log.i(TAG, "onVideoComplete"); 117 | } 118 | 119 | /** 120 | * 激励视频广告被关闭 121 | */ 122 | @Override 123 | public void onADClose() { 124 | Log.i(TAG, "onADClose"); 125 | sendEvent(AdEventAction.onAdClosed); 126 | } 127 | 128 | /** 129 | * 广告流程出错 130 | */ 131 | @Override 132 | public void onError(AdError error) { 133 | String msg = String.format(Locale.getDefault(), "onError, error code: %d, error msg: %s", 134 | error.getErrorCode(), error.getErrorMsg()); 135 | Log.i(TAG, "onError, adError=" + msg); 136 | sendErrorEvent(error.getErrorCode(), error.getErrorMsg()); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /android/src/main/res/layout/activity_ad_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 28 | -------------------------------------------------------------------------------- /android/src/main/res/mipmap-xxhdpi/flutterads_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/android/src/main/res/mipmap-xxhdpi/flutterads_logo.png -------------------------------------------------------------------------------- /android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /doc/SETTING_LOGO.md: -------------------------------------------------------------------------------- 1 | ## 半开屏广告设置 Logo 最佳实践 2 | 本页主要聊聊半开屏广告的 Logo 设置计算规则,帮助你设计一个良好体验的开屏(支持开源项目的最好方式就是点个 Star) 3 | ### 预览效果 4 | |iOS|Android| 5 | |:--:|:--:| 6 | |![](https://raw.githubusercontent.com/FlutterAds/site/master/docs/images/04_iOS_splash.gif)|![](https://github.com/FlutterAds/site/blob/master/docs/images/07_Android_splash.gif?raw=true)| 7 | 8 | 通过上面的预览图可以看出,底部 Logo 在启动页和广告页非常自然的过渡,仿佛就在同一个页面,下面我们看看怎么配置。 9 | ### 曝光规则 10 | 看了各大平台的半开屏广告的其中一个曝光要求是占屏比不低于 `75%`,我们为了保证曝光,让 Logo 的占屏比为 `15%` 左右。 11 | 12 | > 如果你有需求改为动态的需求,可以提 issues 13 | 14 | ### iOS 15 | 默认我们按照高度 `750` 进行计算,,不进行动态适配,至于全面屏是自然就适配的。 16 | - 计算公式 17 | ``` 18 | Logo 区域高度 = 750*15%=112.5 19 | Logo 的高度@1x = 90 20 | Logo 距离底部高度 = (112.5-90)/2=11.25 21 | ``` 22 | - 配置启动 Logo 23 | 24 | 首先这里的「FlutterAds」启动页 Logo 高度是 `90`,你的 Logo 是多少就自己套入公式计算,不要超过 `112.5` 25 | 26 | ![Logo 高度](https://raw.githubusercontent.com/FlutterAds/site/master/docs/images/05_iOS_launchimage.png) 27 | 28 | 然后配置 `LaunchScreen.storyboard`,设置约束距离底部值为 `-11.25` 29 | 30 | ![配置启动页面](https://raw.githubusercontent.com/FlutterAds/site/master/docs/images/06_iOS_LaunchScreen.png) 31 | 32 | - 调用半开屏广告接口 33 | ```Dart 34 | FlutterQqAds.showSplashAd(posId, 'LaunchImage'); 35 | ``` 36 | 37 | ### Android 38 | 39 | 默认我们按照高度 `1920` 进行计算的,算下来是 `96dp`,不进行动态适配 40 | 41 | - 计算公式 42 | 43 | ``` 44 | Logo 区域高度 = 1920*15%=288=96dp 45 | Logo 的高度 = 180/3=60dp 46 | Logo 距离底部高度 = (96-60)/2=18dp 47 | ``` 48 | - 配置启动页 Logo 49 | 50 | 这里启动 Logo 的高度是 `180` 51 | 52 | ![Logo 高度](https://raw.githubusercontent.com/FlutterAds/site/master/docs/images/07_Android_LaunchImage.png) 53 | 54 | 然后我们根据计算公式配置启动页背景 55 | 56 | ![启动页背景](https://github.com/FlutterAds/site/blob/master/docs/images/08_Android_LauncPage.png?raw=true) 57 | 58 | 下面对应代码可以复制修改 59 | 60 | ```xml 61 | 62 | 63 | 64 | 65 | 66 | 67 | 70 | 71 | 72 | 73 | ``` 74 | 修改 `style` 75 | 76 | ![style](https://github.com/FlutterAds/site/blob/master/docs/images/09_Android_style.png?raw=true) 77 | 78 | 设置启动样式 79 | 80 | ![启动样式](https://github.com/FlutterAds/site/blob/master/docs/images/10_Android_launch_theme.png?raw=true) 81 | 82 | - 调用半开屏广告接口 83 | ```Dart 84 | FlutterQqAds.showSplashAd(posId, 'flutterads_logo'); 85 | ``` 86 | > 这里的启动图页 Logo 要放到 `mipmap` 下,放到 `drawable` 下广告页无法找到 87 | 88 | ### 遇到问题 89 | 90 | 如果你遇到问题请提 Issues 给我(提问前建议先搜索尝试,没有再提问) 91 | 92 | ### 支持开源 93 | 94 | 支持开源项目最好的方式就是点一个 Star 95 | 96 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_qq_ads_example 2 |

3 | logo 4 |

5 |

FlutterAds 致力于构建优质的 Flutter 广告插件

6 | 7 | ## 联系作者 8 | - 微信:ZeroFlutter 9 | - 邮箱:zhengsonglan001@gmail.com 10 | 11 | ## 支持开源 12 | 13 | 支持作者 14 | 15 | > 或许你不知道经过了多少个夜晚,就是为了方便你使用 -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | android { 8 | namespace = "com.zero.flutter_qq_ads_example" 9 | compileSdk = 34 10 | ndkVersion = flutter.ndkVersion 11 | 12 | compileOptions { 13 | sourceCompatibility = JavaVersion.VERSION_1_8 14 | targetCompatibility = JavaVersion.VERSION_1_8 15 | } 16 | 17 | kotlinOptions { 18 | jvmTarget = JavaVersion.VERSION_1_8 19 | } 20 | 21 | defaultConfig { 22 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 23 | applicationId = "com.banjixiaoguanjia.app" 24 | // You can update the following values to match your application needs. 25 | // For more information, see: https://flutter.dev/to/review-gradle-config. 26 | minSdk = flutter.minSdkVersion 27 | targetSdk = flutter.targetSdkVersion 28 | versionCode = flutter.versionCode 29 | versionName = flutter.versionName 30 | } 31 | 32 | //签名信息 33 | signingConfigs { 34 | release { 35 | storeFile new File("${project.projectDir}/keystore/flutterads_key.jks") 36 | storePassword "FlutterAds" 37 | keyAlias 'FlutterAdsQQ' 38 | keyPassword "FlutterAds" 39 | //2个版本的签名 40 | v1SigningEnabled true 41 | v2SigningEnabled true 42 | } 43 | } 44 | 45 | buildTypes { 46 | release { 47 | // TODO: Add your own signing config for the release build. 48 | // Signing with the debug keys for now, so `flutter run --release` works. 49 | signingConfig = signingConfigs.debug 50 | } 51 | } 52 | } 53 | 54 | flutter { 55 | source = "../.." 56 | } 57 | 58 | dependencies { 59 | implementation 'androidx.multidex:multidex:2.0.1' 60 | } 61 | -------------------------------------------------------------------------------- /example/android/app/keystore/flutterads_key.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/android/app/keystore/flutterads_key.jks -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 9 | 10 | 16 | 24 | 28 | 32 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/zero/flutter_qq_ads_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zero.flutter_qq_ads_example; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java: -------------------------------------------------------------------------------- 1 | // Generated file. 2 | // 3 | // If you wish to remove Flutter's multidex support, delete this entire file. 4 | // 5 | // Modifications to this file should be done in a copy under a different name 6 | // as this file may be regenerated. 7 | 8 | package io.flutter.app; 9 | 10 | import android.app.Application; 11 | import android.content.Context; 12 | import androidx.annotation.CallSuper; 13 | import androidx.multidex.MultiDex; 14 | 15 | /** 16 | * Extension of {@link android.app.Application}, adding multidex support. 17 | */ 18 | public class FlutterMultiDexApplication extends Application { 19 | @Override 20 | @CallSuper 21 | protected void attachBaseContext(Context base) { 22 | super.attachBaseContext(base); 23 | MultiDex.install(this); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/flutterads_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/android/app/src/main/res/mipmap-xxhdpi/flutterads_logo.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/flutterads_logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/android/app/src/main/res/mipmap-xxhdpi/flutterads_logo2.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | android.injected.testOnly=false -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "7.3.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /example/images/gromore_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/images/gromore_1.png -------------------------------------------------------------------------------- /example/images/gromore_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/images/gromore_2.png -------------------------------------------------------------------------------- /example/images/gromore_pro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/images/gromore_pro.png -------------------------------------------------------------------------------- /example/images/pro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/images/pro.png -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 32 | end 33 | 34 | post_install do |installer| 35 | installer.pods_project.targets.each do |target| 36 | flutter_additional_ios_build_settings(target) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "20x20", 5 | "idiom": "iphone", 6 | "filename": "icon-20@2x.png", 7 | "scale": "2x" 8 | }, 9 | { 10 | "size": "20x20", 11 | "idiom": "iphone", 12 | "filename": "icon-20@3x.png", 13 | "scale": "3x" 14 | }, 15 | { 16 | "size": "29x29", 17 | "idiom": "iphone", 18 | "filename": "icon-29.png", 19 | "scale": "1x" 20 | }, 21 | { 22 | "size": "29x29", 23 | "idiom": "iphone", 24 | "filename": "icon-29@2x.png", 25 | "scale": "2x" 26 | }, 27 | { 28 | "size": "29x29", 29 | "idiom": "iphone", 30 | "filename": "icon-29@3x.png", 31 | "scale": "3x" 32 | }, 33 | { 34 | "size": "40x40", 35 | "idiom": "iphone", 36 | "filename": "icon-40@2x.png", 37 | "scale": "2x" 38 | }, 39 | { 40 | "size": "40x40", 41 | "idiom": "iphone", 42 | "filename": "icon-40@3x.png", 43 | "scale": "3x" 44 | }, 45 | { 46 | "size": "60x60", 47 | "idiom": "iphone", 48 | "filename": "icon-60@2x.png", 49 | "scale": "2x" 50 | }, 51 | { 52 | "size": "60x60", 53 | "idiom": "iphone", 54 | "filename": "icon-60@3x.png", 55 | "scale": "3x" 56 | }, 57 | { 58 | "size": "20x20", 59 | "idiom": "ipad", 60 | "filename": "icon-20-ipad.png", 61 | "scale": "1x" 62 | }, 63 | { 64 | "size": "20x20", 65 | "idiom": "ipad", 66 | "filename": "icon-20@2x-ipad.png", 67 | "scale": "2x" 68 | }, 69 | { 70 | "size": "29x29", 71 | "idiom": "ipad", 72 | "filename": "icon-29-ipad.png", 73 | "scale": "1x" 74 | }, 75 | { 76 | "size": "29x29", 77 | "idiom": "ipad", 78 | "filename": "icon-29@2x-ipad.png", 79 | "scale": "2x" 80 | }, 81 | { 82 | "size": "40x40", 83 | "idiom": "ipad", 84 | "filename": "icon-40.png", 85 | "scale": "1x" 86 | }, 87 | { 88 | "size": "40x40", 89 | "idiom": "ipad", 90 | "filename": "icon-40@2x.png", 91 | "scale": "2x" 92 | }, 93 | { 94 | "size": "76x76", 95 | "idiom": "ipad", 96 | "filename": "icon-76.png", 97 | "scale": "1x" 98 | }, 99 | { 100 | "size": "76x76", 101 | "idiom": "ipad", 102 | "filename": "icon-76@2x.png", 103 | "scale": "2x" 104 | }, 105 | { 106 | "size": "83.5x83.5", 107 | "idiom": "ipad", 108 | "filename": "icon-83.5@2x.png", 109 | "scale": "2x" 110 | }, 111 | { 112 | "size": "1024x1024", 113 | "idiom": "ios-marketing", 114 | "filename": "icon-1024.png", 115 | "scale": "1x" 116 | } 117 | ], 118 | "info": { 119 | "version": 1, 120 | "author": "icon.wuruihong.com" 121 | } 122 | } -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage@1x.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage2@1x.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage2@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage2@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage2.imageset/LaunchImage2@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/LaunchImage2.imageset/LaunchImage2@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage2.imageset/LaunchImage2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/LaunchImage2.imageset/LaunchImage2@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage2.imageset/LaunchImage2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/example/ios/Runner/Assets.xcassets/LaunchImage2.imageset/LaunchImage2@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | FlutterAds 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | flutter_qq_ads_example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | NSUserTrackingUsageDescription 33 | 为了向您提供更优质、安全的个性化服务及内容,需要您允许使用相关权限 34 | UILaunchStoryboardName 35 | LaunchScreen 36 | UIMainStoryboardFile 37 | Main 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | UIViewControllerBasedStatusBarAppearance 52 | 53 | CADisableMinimumFrameDurationOnPhone 54 | 55 | UIApplicationSupportsIndirectInputEvents 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/ads_config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | /// 广告配置信息 4 | class AdsConfig { 5 | /// 获取 Logo 资源名称 6 | static String get logo { 7 | if (Platform.isAndroid) { 8 | return 'flutterads_logo'; 9 | } else { 10 | return 'LaunchImage'; 11 | } 12 | } 13 | 14 | /// 获取 Logo 资源名称 2 15 | static String get logo2 { 16 | if (Platform.isAndroid) { 17 | return 'flutterads_logo2'; 18 | } else { 19 | return 'LaunchImage2'; 20 | } 21 | } 22 | 23 | /// 获取 App id 24 | static String get appId { 25 | return Platform.isAndroid ? '1204814627' : '1204814641'; 26 | } 27 | 28 | /// 获取开屏广告位id 29 | static String get splashId { 30 | return Platform.isAndroid ? '4046660274345204' : '1046662224832381'; 31 | } 32 | 33 | /// 获取插屏广告位id 34 | static String get interstitialId { 35 | return Platform.isAndroid ? '1066865274941328' : '7046066294830767'; 36 | } 37 | 38 | /// 获取插屏全屏视频广告位id 39 | static String get interstitialFullScreenVideoId { 40 | if (Platform.isAndroid) { 41 | return '3012521499614895'; 42 | } else { 43 | return '3092322459911886'; 44 | } 45 | } 46 | 47 | /// 获取插屏激励视频广告位id 48 | static String get interstitialRewardVideoId { 49 | if (Platform.isAndroid) { 50 | return '2052820580637311'; 51 | } else { 52 | return '9022927550132316'; 53 | } 54 | } 55 | 56 | /// 获取激励视频广告位id 57 | static String get rewardVideoId { 58 | if (Platform.isAndroid) { 59 | return '6086667264144219'; 60 | } else { 61 | return '3066367234238599'; 62 | } 63 | } 64 | 65 | /// 获取 Banner 广告位id 66 | static String get bannerId { 67 | if (Platform.isAndroid) { 68 | return '5086068204047616'; 69 | } else { 70 | return '7026465284342149'; 71 | } 72 | } 73 | 74 | /// 获取信息流广告位id 75 | static String get feedId { 76 | if (Platform.isAndroid) { 77 | return '7036167274246466'; 78 | } else { 79 | return '8076264204347078'; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /example/lib/feed_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_qq_ads/flutter_qq_ads.dart'; 3 | import 'package:loadany/loadany.dart'; 4 | 5 | import 'ads_config.dart'; 6 | 7 | /// 信息流页面 8 | class FeedPage extends StatefulWidget { 9 | FeedPage({Key? key}) : super(key: key); 10 | 11 | @override 12 | _FeedPageState createState() => _FeedPageState(); 13 | } 14 | 15 | class _FeedPageState extends State { 16 | List feedList = []; 17 | List feedAdList = []; 18 | 19 | LoadStatus loadStatus = LoadStatus.normal; 20 | 21 | @override 22 | void initState() { 23 | getFeedList(); 24 | super.initState(); 25 | } 26 | 27 | @override 28 | void dispose() { 29 | clearFeedAd(); 30 | super.dispose(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | appBar: AppBar( 37 | title: Text('信息流(FlutterAds)'), 38 | ), 39 | body: LoadAny( 40 | onLoadMore: () async { 41 | getFeedList(); 42 | }, 43 | status: loadStatus, 44 | child: CustomScrollView( 45 | slivers: [ 46 | SliverList( 47 | delegate: SliverChildBuilderDelegate( 48 | (BuildContext context, int index) { 49 | if (index % 10 == 4) { 50 | int adIndex = index ~/ 10; 51 | print('adIndex:$adIndex'); 52 | if (adIndex >= feedAdList.length) { 53 | return Container( 54 | height: 80, 55 | width: double.maxFinite, 56 | color: Colors.blueAccent, 57 | alignment: Alignment.centerLeft, 58 | child: Text('暂无广告 $index'), 59 | ); 60 | } 61 | 62 | int adId = feedAdList[adIndex]; 63 | return Container( 64 | width: MediaQuery.of(context).size.width, 65 | alignment: Alignment.center, 66 | child: AdFeedWidget( 67 | posId: '$adId', 68 | width: 375, 69 | height: 256, 70 | show: true, 71 | ), 72 | ); 73 | } 74 | return LoadingItemWidget(); 75 | }, 76 | childCount: feedList.length, 77 | ), 78 | ) 79 | ], 80 | ), 81 | ), 82 | ); 83 | } 84 | 85 | /// 加载信息流 86 | Future getFeedList() async { 87 | setState(() { 88 | loadStatus = LoadStatus.loading; 89 | }); 90 | await Future.delayed(Duration(seconds: 2)); 91 | for (var i = 0; i < 30; i++) { 92 | feedList.add(i); 93 | } 94 | getFeedAdList(); 95 | } 96 | 97 | // 加载信息流广告 98 | Future getFeedAdList() async { 99 | try { 100 | // int feedIdIndex = feedAdList.length ~/ 3 % AdsConfig.feedIdList.length; 101 | // print('feedIdIndex:$feedIdIndex'); 102 | List adResultList = await FlutterQqAds.loadFeedAd( 103 | AdsConfig.feedId, 104 | count: 3, 105 | ); 106 | feedAdList.addAll(adResultList); 107 | } catch (e) { 108 | print(e.toString()); 109 | } 110 | setState(() { 111 | loadStatus = LoadStatus.normal; 112 | }); 113 | } 114 | 115 | // 清除信息流广告 116 | Future clearFeedAd() async { 117 | bool result = await FlutterQqAds.clearFeedAd(feedAdList); 118 | print('clearFeedAd:$result'); 119 | } 120 | } 121 | 122 | /// 加载项组件 123 | class LoadingItemWidget extends StatelessWidget { 124 | const LoadingItemWidget({ 125 | Key? key, 126 | }) : super(key: key); 127 | 128 | @override 129 | Widget build(BuildContext context) { 130 | return Container( 131 | height: 80, 132 | width: double.maxFinite, 133 | alignment: Alignment.centerLeft, 134 | padding: EdgeInsets.all(10), 135 | margin: EdgeInsets.symmetric(vertical: 10), 136 | color: Colors.white, 137 | child: Row( 138 | children: [ 139 | Container( 140 | width: 60, 141 | height: 60, 142 | decoration: BoxDecoration( 143 | shape: BoxShape.circle, 144 | color: Color(0xFFEBEBF4), 145 | ), 146 | ), 147 | SizedBox(width: 20), 148 | Expanded( 149 | child: Padding( 150 | padding: const EdgeInsets.all(4), 151 | child: Column( 152 | crossAxisAlignment: CrossAxisAlignment.start, 153 | children: [ 154 | Container( 155 | width: double.maxFinite, 156 | height: 20, 157 | decoration: BoxDecoration( 158 | color: Color(0xFFEBEBF4), 159 | ), 160 | ), 161 | Spacer(), 162 | Container( 163 | width: 200, 164 | height: 20, 165 | decoration: BoxDecoration( 166 | color: Color(0xFFE4E4F4), 167 | ), 168 | ), 169 | ], 170 | ), 171 | ), 172 | ) 173 | ], 174 | ), 175 | ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /example/lib/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_qq_ads/flutter_qq_ads.dart'; 6 | 7 | import 'ads_config.dart'; 8 | import 'feed_page.dart'; 9 | 10 | // 结果信息 11 | String _result = ''; 12 | 13 | class HomePage extends StatefulWidget { 14 | @override 15 | _HomePageState createState() => _HomePageState(); 16 | } 17 | 18 | class _HomePageState extends State { 19 | String _adEvent = ''; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | appBar: AppBar( 30 | title: const Text('FlutterAds QQ plugin'), 31 | ), 32 | body: Center( 33 | child: SingleChildScrollView( 34 | padding: const EdgeInsets.all(10), 35 | child: Column( 36 | children: [ 37 | SizedBox(height: 10), 38 | Text('Result: $_result'), 39 | SizedBox(height: 10), 40 | Text('onAdEvent: $_adEvent'), 41 | SizedBox(height: 20), 42 | Row( 43 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 44 | children: [ 45 | ElevatedButton( 46 | child: Text('初始化'), 47 | onPressed: () {}, 48 | ), 49 | SizedBox(height: 20), 50 | ElevatedButton( 51 | child: Text('请求跟踪授权'), 52 | onPressed: () { 53 | requestIDFA(); 54 | }, 55 | ), 56 | SizedBox(height: 20), 57 | ElevatedButton( 58 | child: Text('个性化广告'), 59 | onPressed: () { 60 | setPersonalizedAd(1); 61 | }, 62 | ), 63 | ], 64 | ), 65 | Row( 66 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 67 | children: [ 68 | ElevatedButton( 69 | child: Text('开屏(Logo2)'), 70 | onPressed: () { 71 | showSplashAd(AdsConfig.logo2); 72 | }, 73 | ), 74 | SizedBox(height: 20), 75 | ElevatedButton( 76 | child: Text('开屏(全屏)'), 77 | onPressed: () { 78 | showSplashAd(); 79 | setState(() {}); 80 | }, 81 | ), 82 | ], 83 | ), 84 | Row( 85 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 86 | children: [ 87 | ElevatedButton( 88 | child: Text('插屏广告'), 89 | onPressed: () { 90 | showInterstitialAd(AdsConfig.interstitialId); 91 | }, 92 | ), 93 | SizedBox(height: 20), 94 | ElevatedButton( 95 | child: Text('插全屏广告'), 96 | onPressed: () { 97 | showInterstitialAd( 98 | AdsConfig.interstitialFullScreenVideoId, 99 | showFullScreenVideo: true, 100 | ); 101 | }, 102 | ), 103 | ], 104 | ), 105 | Row( 106 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 107 | children: [ 108 | ElevatedButton( 109 | child: Text('插屏激励'), 110 | onPressed: () { 111 | showInterstitialAd( 112 | AdsConfig.interstitialRewardVideoId, 113 | showFullScreenVideo: true, 114 | showRewardVideo: true, 115 | ); 116 | }, 117 | ), 118 | SizedBox(height: 20), 119 | ElevatedButton( 120 | child: Text('激励视频'), 121 | onPressed: () { 122 | showRewardVideoAd(); 123 | }, 124 | ), 125 | ], 126 | ), 127 | SizedBox(height: 20), 128 | ElevatedButton( 129 | child: Text('信息流'), 130 | onPressed: () { 131 | Navigator.push( 132 | context, 133 | MaterialPageRoute( 134 | builder: (context) => FeedPage(), 135 | )); 136 | }, 137 | ), 138 | SizedBox(height: 20), 139 | const Center(child: Text('👇🏻 Banner 广告 👇🏻')), 140 | const SizedBox(height: 10), 141 | AdBannerWidget( 142 | posId: AdsConfig.bannerId, 143 | width: 300, 144 | height: 80, 145 | interval: 0, 146 | show: true, 147 | ), 148 | SizedBox(height: 10), 149 | ], 150 | ), 151 | ), 152 | ), 153 | ); 154 | } 155 | 156 | /// 设置广告监听 157 | Future setAdEvent() async { 158 | setState(() { 159 | _adEvent = '设置成功'; 160 | }); 161 | FlutterQqAds.onEventListener((event) { 162 | _adEvent = 'adId:${event.adId} action:${event.action}'; 163 | if (event is AdErrorEvent) { 164 | // 错误事件 165 | _adEvent += ' errCode:${event.errCode} errMsg:${event.errMsg}'; 166 | } else if (event is AdRewardEvent) { 167 | // 激励事件 168 | _adEvent += 169 | ' transId:${event.transId} customData:${event.customData} userId:${event.userId}'; 170 | } 171 | print('onEventListener:$_adEvent'); 172 | setState(() {}); 173 | }); 174 | } 175 | 176 | /// 请求应用跟踪透明度授权 177 | Future requestIDFA() async { 178 | bool result = await FlutterQqAds.requestIDFA; 179 | _adEvent = '请求广告标识符:$result'; 180 | setState(() {}); 181 | } 182 | 183 | /// 设置个性化广告 184 | /// [state] 0:不限制 1:限制 185 | Future setPersonalizedAd(int state) async { 186 | bool result = await FlutterQqAds.setPersonalizedState(state); 187 | _adEvent = '设置个性化广告:$result'; 188 | setState(() {}); 189 | } 190 | 191 | /// 展示插屏广告 192 | Future showInterstitialAd( 193 | String posId, { 194 | bool showFullScreenVideo = false, 195 | bool showRewardVideo = false, 196 | }) async { 197 | try { 198 | bool result = await FlutterQqAds.showInterstitialAd( 199 | posId, 200 | showPopup: false, 201 | showFullScreenVideo: showFullScreenVideo, 202 | showRewardVideo: showRewardVideo, 203 | autoPlayMuted: false, 204 | autoPlayOnWifi: false, 205 | userId: 'userId', 206 | customData: 'showInterstitialAd customData', 207 | ); 208 | _result = "展示插屏广告${result ? '成功' : '失败'}"; 209 | } on PlatformException catch (e) { 210 | _result = "展示插屏广告失败 code:${e.code} msg:${e.message} details:${e.details}"; 211 | } 212 | setState(() {}); 213 | } 214 | 215 | /// 展示激励视频广告 216 | Future showRewardVideoAd() async { 217 | try { 218 | bool result = await FlutterQqAds.showRewardVideoAd( 219 | AdsConfig.rewardVideoId, 220 | playMuted: false, 221 | customData: 'showRewardVideoAd customData', 222 | userId: 'userId', 223 | ); 224 | _result = "展示激励视频广告${result ? '成功' : '失败'}"; 225 | } on PlatformException catch (e) { 226 | _result = 227 | "展示激励视频广告失败 code:${e.code} msg:${e.message} details:${e.details}"; 228 | } 229 | setState(() {}); 230 | } 231 | } 232 | 233 | /// 展示开屏广告 234 | /// [logo] 展示如果传递则展示logo,不传递不展示 235 | Future showSplashAd([String? logo]) async { 236 | try { 237 | bool result = await FlutterQqAds.showSplashAd( 238 | AdsConfig.splashId, 239 | logo: logo, 240 | fetchDelay: 3, 241 | ); 242 | _result = "展示开屏广告${result ? '成功' : '失败'}"; 243 | } on PlatformException catch (e) { 244 | _result = "展示开屏广告失败 code:${e.code} msg:${e.message} details:${e.details}"; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_qq_ads/flutter_qq_ads.dart'; 3 | 4 | import 'ads_config.dart'; 5 | import 'pages/home_page.dart'; 6 | 7 | void main() { 8 | // 绑定引擎 9 | WidgetsFlutterBinding.ensureInitialized(); 10 | setAdEvent(); 11 | init().then((value) { 12 | if (value) { 13 | FlutterQqAds.showSplashAd( 14 | AdsConfig.splashId, 15 | logo: AdsConfig.logo, 16 | ); 17 | } 18 | }); 19 | 20 | // 启动 21 | runApp(MyApp()); 22 | } 23 | 24 | class MyApp extends StatefulWidget { 25 | @override 26 | _MyAppState createState() => _MyAppState(); 27 | } 28 | 29 | class _MyAppState extends State { 30 | @override 31 | Widget build(BuildContext context) { 32 | return MaterialApp( 33 | home: HomePage(), 34 | ); 35 | } 36 | } 37 | 38 | /// 初始化广告 SDK 39 | Future init() async { 40 | bool result = await FlutterQqAds.initAd(AdsConfig.appId); 41 | debugPrint("广告SDK 初始化${result ? '成功' : '失败'}"); 42 | return result; 43 | } 44 | 45 | /// 设置广告监听 46 | Future setAdEvent() async { 47 | FlutterQqAds.onEventListener((event) { 48 | debugPrint('adId:${event.adId} action:${event.action}'); 49 | if (event is AdErrorEvent) { 50 | // 错误事件 51 | debugPrint(' errCode:${event.errCode} errMsg:${event.errMsg}'); 52 | } else if (event is AdRewardEvent) { 53 | // 激励事件 54 | debugPrint( 55 | ' transId:${event.transId} userId:${event.userId} customData:${event.customData}'); 56 | } 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /example/lib/pages/banner_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_qq_ads/flutter_qq_ads.dart'; 3 | 4 | import '../ads_config.dart'; 5 | import '../widgets/widgets.dart'; 6 | 7 | class BannerPage extends StatefulWidget { 8 | const BannerPage({Key? key}) : super(key: key); 9 | 10 | @override 11 | _BannerPageState createState() => _BannerPageState(); 12 | } 13 | 14 | class _BannerPageState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | appBar: buildAppBar(context, 'Banner广告'), 19 | body: Center( 20 | child: Column( 21 | mainAxisSize: MainAxisSize.max, 22 | mainAxisAlignment: MainAxisAlignment.center, 23 | children: [ 24 | SizedBox(height: 20), 25 | AdBannerWidget( 26 | posId: AdsConfig.bannerId, 27 | width: 300, 28 | height: 80, 29 | interval: 30, 30 | show: true, 31 | ), 32 | ], 33 | ), 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/lib/pages/feed_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_qq_ads/flutter_qq_ads.dart'; 3 | import 'package:loadany/loadany.dart'; 4 | 5 | import '../ads_config.dart'; 6 | import '../widgets/widgets.dart'; 7 | 8 | /// 信息流页面 9 | class FeedPage extends StatefulWidget { 10 | FeedPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | _FeedPageState createState() => _FeedPageState(); 14 | } 15 | 16 | class _FeedPageState extends State { 17 | List feedList = []; 18 | List feedAdList = []; 19 | 20 | LoadStatus loadStatus = LoadStatus.normal; 21 | 22 | @override 23 | void initState() { 24 | getFeedList(); 25 | super.initState(); 26 | } 27 | 28 | @override 29 | void dispose() { 30 | clearFeedAd(); 31 | super.dispose(); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return Scaffold( 37 | appBar: buildAppBar(context, '信息流广告'), 38 | body: LoadAny( 39 | onLoadMore: () async { 40 | getFeedList(); 41 | }, 42 | status: loadStatus, 43 | child: CustomScrollView( 44 | slivers: [ 45 | SliverList( 46 | delegate: SliverChildBuilderDelegate( 47 | (BuildContext context, int index) { 48 | if (index % 5 == 4) { 49 | int adIndex = index ~/ 5; 50 | if (adIndex >= feedAdList.length) { 51 | return Container( 52 | height: 80, 53 | width: double.maxFinite, 54 | color: Colors.blueAccent, 55 | alignment: Alignment.centerLeft, 56 | child: Text('暂无广告 $index'), 57 | ); 58 | } 59 | 60 | int adId = feedAdList[adIndex]; 61 | return AdFeedWidget( 62 | posId: '$adId', 63 | width: 375, 64 | height: 256, 65 | show: true, 66 | ); 67 | } 68 | return LoadingItemWidget(); 69 | }, 70 | childCount: feedList.length, 71 | ), 72 | ) 73 | ], 74 | ), 75 | ), 76 | ); 77 | } 78 | 79 | /// 加载信息流 80 | Future getFeedList() async { 81 | setState(() { 82 | loadStatus = LoadStatus.loading; 83 | }); 84 | await Future.delayed(Duration(seconds: 2)); 85 | for (var i = 0; i < 30; i++) { 86 | feedList.add(i); 87 | } 88 | getFeedAdList(); 89 | } 90 | 91 | // 加载信息流广告 92 | Future getFeedAdList() async { 93 | try { 94 | List adResultList = await FlutterQqAds.loadFeedAd( 95 | AdsConfig.feedId, 96 | count: 3, 97 | ); 98 | feedAdList.addAll(adResultList); 99 | } catch (e) { 100 | print(e.toString()); 101 | } 102 | setState(() { 103 | loadStatus = LoadStatus.normal; 104 | }); 105 | } 106 | 107 | // 清除信息流广告 108 | Future clearFeedAd() async { 109 | bool result = await FlutterQqAds.clearFeedAd(feedAdList); 110 | print('clearFeedAd:$result'); 111 | } 112 | } 113 | 114 | /// 加载项组件 115 | class LoadingItemWidget extends StatelessWidget { 116 | const LoadingItemWidget({ 117 | Key? key, 118 | }) : super(key: key); 119 | 120 | @override 121 | Widget build(BuildContext context) { 122 | return Container( 123 | height: 80, 124 | width: double.maxFinite, 125 | alignment: Alignment.centerLeft, 126 | padding: EdgeInsets.all(10), 127 | margin: EdgeInsets.symmetric(vertical: 10), 128 | color: Colors.white, 129 | child: Row( 130 | children: [ 131 | Container( 132 | width: 60, 133 | height: 60, 134 | decoration: BoxDecoration( 135 | shape: BoxShape.circle, 136 | color: Color(0xFFEBEBF4), 137 | ), 138 | ), 139 | SizedBox(width: 20), 140 | Expanded( 141 | child: Padding( 142 | padding: const EdgeInsets.all(4), 143 | child: Column( 144 | crossAxisAlignment: CrossAxisAlignment.start, 145 | children: [ 146 | Container( 147 | width: double.maxFinite, 148 | height: 20, 149 | decoration: BoxDecoration( 150 | color: Color(0xFFEBEBF4), 151 | ), 152 | ), 153 | Spacer(), 154 | Container( 155 | width: 200, 156 | height: 20, 157 | decoration: BoxDecoration( 158 | color: Color(0xFFE4E4F4), 159 | ), 160 | ), 161 | ], 162 | ), 163 | ), 164 | ) 165 | ], 166 | ), 167 | ); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /example/lib/pages/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_qq_ads/flutter_qq_ads.dart'; 5 | import '../router/router.dart'; 6 | import '../theme/style.dart'; 7 | import 'banner_page.dart'; 8 | import 'feed_page.dart'; 9 | 10 | import 'interstitial_page.dart'; 11 | import 'reward_video_page.dart'; 12 | import 'splash_page.dart'; 13 | 14 | class HomePage extends StatefulWidget { 15 | @override 16 | _HomePageState createState() => _HomePageState(); 17 | } 18 | 19 | class _HomePageState extends State { 20 | @override 21 | void initState() { 22 | super.initState(); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | appBar: AppBar( 29 | title: const Text('FlutterAds QQ plugin'), 30 | ), 31 | body: SafeArea( 32 | child: SingleChildScrollView( 33 | child: Center( 34 | child: Column( 35 | children: [ 36 | InkWell( 37 | child: Image.asset( 38 | 'images/gromore_pro.png', 39 | width: double.maxFinite, 40 | ), 41 | onTap: () => pushProPage(context), 42 | ), 43 | kDivider, 44 | ListTile( 45 | dense: true, 46 | title: Text( 47 | '📢 优量汇测试广告位有限制而且还经常冻结,请使用 GroMore 体验', 48 | style: TextStyle(color: Colors.amber), 49 | ), 50 | onTap: () => pushProPage(context), 51 | ), 52 | kDivider, 53 | ListTile( 54 | dense: true, 55 | title: Text('🍎 请求应用跟踪透明授权(iOS)'), 56 | onTap: () => requestIDFA(), 57 | ), 58 | kDivider, 59 | ListTile( 60 | dense: true, 61 | title: Text('🌈 设置个性化广告'), 62 | onTap: () => setPersonalizedAd(1), 63 | ), 64 | kDivider, 65 | ListTile( 66 | title: Text('开屏广告'), 67 | onTap: () => pushPage(context, SplashPage()), 68 | ), 69 | kDivider, 70 | ListTile( 71 | title: Text('插屏广告'), 72 | onTap: () => pushPage(context, InterstitialPage()), 73 | ), 74 | kDivider, 75 | ListTile( 76 | title: Text('Banner 广告'), 77 | onTap: () => pushPage(context, BannerPage()), 78 | ), 79 | kDivider, 80 | ListTile( 81 | title: Text('激励视频广告'), 82 | onTap: () => pushPage(context, RewardVideoPage()), 83 | ), 84 | kDivider, 85 | ListTile( 86 | title: Text('信息流'), 87 | onTap: () => pushPage(context, FeedPage()), 88 | ), 89 | kDivider, 90 | ], 91 | ), 92 | ), 93 | ), 94 | ), 95 | ); 96 | } 97 | 98 | /// 请求应用跟踪透明度授权 99 | Future requestIDFA() async { 100 | bool result = await FlutterQqAds.requestIDFA; 101 | print('请求广告标识符:$result'); 102 | } 103 | 104 | /// 设置个性化广告 105 | /// [state] 0:不限制 1:限制 106 | Future setPersonalizedAd(int state) async { 107 | bool result = await FlutterQqAds.setPersonalizedState(state); 108 | print('设置个性化广告:$result'); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /example/lib/pages/interstitial_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_qq_ads/flutter_qq_ads.dart'; 3 | 4 | import '../ads_config.dart'; 5 | import '../theme/style.dart'; 6 | import '../widgets/widgets.dart'; 7 | 8 | // 插屏视频 9 | class InterstitialPage extends StatefulWidget { 10 | const InterstitialPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | _InterstitialPageState createState() => _InterstitialPageState(); 14 | } 15 | 16 | class _InterstitialPageState extends State { 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | appBar: buildAppBar(context, '插屏广告'), 21 | body: Column( 22 | children: [ 23 | ListTile( 24 | title: Text('插屏-半屏'), 25 | onTap: () => showInterstitialAd(AdsConfig.interstitialId), 26 | ), 27 | kDivider, 28 | ListTile( 29 | title: Text('插屏-全屏'), 30 | onTap: () => showInterstitialAd( 31 | AdsConfig.interstitialFullScreenVideoId, 32 | showFullScreenVideo: true), 33 | ), 34 | kDivider, 35 | ListTile( 36 | title: Text('插屏-激励视频'), 37 | onTap: () => showInterstitialAd(AdsConfig.interstitialRewardVideoId, 38 | showRewardVideo: true), 39 | ), 40 | kDivider, 41 | ], 42 | ), 43 | ); 44 | } 45 | 46 | /// 展示插屏广告 47 | Future showInterstitialAd(String posId, 48 | {bool showFullScreenVideo = false, bool showRewardVideo = false}) async { 49 | bool result = await FlutterQqAds.showInterstitialAd( 50 | posId, 51 | showPopup: false, 52 | showFullScreenVideo: showFullScreenVideo, 53 | showRewardVideo: showRewardVideo, 54 | autoPlayMuted: false, 55 | autoPlayOnWifi: false, 56 | userId: 'userId', 57 | customData: 'showInterstitialAd customData', 58 | ); 59 | print("展示插屏广告${result ? '成功' : '失败'}"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /example/lib/pages/pro_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Pro 页面 4 | class ProPage extends StatefulWidget { 5 | const ProPage({Key? key}) : super(key: key); 6 | 7 | @override 8 | State createState() => _ProPageState(); 9 | } 10 | 11 | class _ProPageState extends State { 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | appBar: AppBar( 16 | title: const Text('Pro 版体验'), 17 | ), 18 | body: SingleChildScrollView( 19 | child: Column( 20 | children: [ 21 | const Center( 22 | child: Padding( 23 | padding: EdgeInsets.all(10.0), 24 | child: Text( 25 | '开源版不提供示例,请扫码下载 Pro 版体验', 26 | textAlign: TextAlign.center, 27 | ), 28 | ), 29 | ), 30 | Image.network( 31 | 'https://flutterads.top/gzh_qrcode.webp', 32 | fit: BoxFit.cover, 33 | ), 34 | const Center( 35 | child: Padding( 36 | padding: EdgeInsets.all(10.0), 37 | child: Text( 38 | '扫码关注公众号回复【Pro】,即可下载体验', 39 | textAlign: TextAlign.center, 40 | style: TextStyle( 41 | color: Colors.purple, 42 | fontSize: 16, 43 | fontWeight: FontWeight.bold, 44 | ), 45 | ), 46 | ), 47 | ), 48 | ], 49 | ), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/lib/pages/reward_video_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_qq_ads/flutter_qq_ads.dart'; 3 | 4 | import '../ads_config.dart'; 5 | import '../theme/style.dart'; 6 | import '../widgets/widgets.dart'; 7 | 8 | // 激励视频 9 | class RewardVideoPage extends StatefulWidget { 10 | const RewardVideoPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | _RewardVideoPageState createState() => _RewardVideoPageState(); 14 | } 15 | 16 | class _RewardVideoPageState extends State { 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | appBar: buildAppBar(context, '激励视频'), 21 | body: Column( 22 | children: [ 23 | ListTile( 24 | title: Text('激励视频'), 25 | onTap: () => showRewardVideoAd(AdsConfig.rewardVideoId), 26 | ), 27 | kDivider, 28 | ], 29 | ), 30 | ); 31 | } 32 | 33 | /// 展示激励视频广告 34 | /// [posId] 广告位id 35 | Future showRewardVideoAd(String posId) async { 36 | bool result = await FlutterQqAds.showRewardVideoAd( 37 | AdsConfig.rewardVideoId, 38 | playMuted: false, 39 | customData: 'showRewardVideoAd customData', 40 | userId: 'userId', 41 | ); 42 | print("展示激励视频广告${result ? '成功' : '失败'}"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/lib/pages/splash_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_qq_ads/flutter_qq_ads.dart'; 3 | 4 | import '../ads_config.dart'; 5 | import '../theme/style.dart'; 6 | import '../widgets/widgets.dart'; 7 | 8 | // 开屏 9 | class SplashPage extends StatefulWidget { 10 | const SplashPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | _SplashPageState createState() => _SplashPageState(); 14 | } 15 | 16 | class _SplashPageState extends State { 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | appBar: buildAppBar(context, '开屏广告'), 21 | body: Column( 22 | children: [ 23 | ListTile( 24 | title: Text('开屏(全屏)'), 25 | onTap: () => showSplashAd(), 26 | ), 27 | kDivider, 28 | ListTile( 29 | title: Text('开屏-Logo1'), 30 | onTap: () => showSplashAd(AdsConfig.logo), 31 | ), 32 | kDivider, 33 | ListTile( 34 | title: Text('开屏-Logo2'), 35 | onTap: () => showSplashAd(AdsConfig.logo2), 36 | ), 37 | kDivider, 38 | ], 39 | ), 40 | ); 41 | } 42 | 43 | /// 展示开屏广告 44 | /// [logo] 展示如果传递则展示logo,不传递不展示 45 | Future showSplashAd([String? logo]) async { 46 | bool result = await FlutterQqAds.showSplashAd( 47 | AdsConfig.splashId, 48 | logo: logo, 49 | ); 50 | print("展示开屏广告${result ? '成功' : '失败'}"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/lib/router/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../pages/pro_page.dart'; 4 | 5 | // 去对应的页面 6 | void pushPage(BuildContext context, Widget page) { 7 | Navigator.push(context, MaterialPageRoute(builder: (context) => page)); 8 | } 9 | 10 | // 去 Pro 页面 11 | void pushProPage(BuildContext context) { 12 | pushPage(context, ProPage()); 13 | } 14 | -------------------------------------------------------------------------------- /example/lib/theme/style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // 横线 4 | const kDivider = const Divider(height: 0.5); 5 | -------------------------------------------------------------------------------- /example/lib/widgets/widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../router/router.dart'; 4 | 5 | // 构建 AppBar 6 | AppBar buildAppBar(BuildContext context, String title) { 7 | return AppBar( 8 | title: Text('$title (FlutterAds)'), 9 | actions: [ 10 | // 去 Pro 页面 11 | IconButton( 12 | icon: Image.asset('images/pro.png'), 13 | onPressed: () => pushProPage(context), 14 | ), 15 | ], 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_qq_ads_example 2 | description: FlutterAds 优量汇广告 SDK 插件示例 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 7 | version: 2.9.0+24 8 | 9 | environment: 10 | sdk: ">=2.12.0 <3.0.0" 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | 16 | flutter_qq_ads: 17 | # When depending on this package from a real application you should use: 18 | # flutter_qq_ads: ^x.y.z 19 | # See https://dart.dev/tools/pub/dependencies#version-constraints 20 | # The example app is bundled with the plugin so we use a path dependency on 21 | # the parent directory to use the current plugin's version. 22 | path: ../ 23 | 24 | # The following adds the Cupertino Icons font to your application. 25 | # Use with the CupertinoIcons class for iOS style icons. 26 | cupertino_icons: ^1.0.4 27 | loadany: ^1.0.0 28 | 29 | dev_dependencies: 30 | flutter_test: 31 | sdk: flutter 32 | 33 | # For information on the generic Dart part of this file, see the 34 | # following page: https://dart.dev/tools/pub/pubspec 35 | 36 | # The following section is specific to Flutter. 37 | flutter: 38 | # The following line ensures that the Material Icons font is 39 | # included with your application, so that you can use the icons in 40 | # the material Icons class. 41 | uses-material-design: true 42 | assets: 43 | - images/ 44 | # To add assets to your application, add an assets section, like this: 45 | # assets: 46 | # - images/a_dot_burr.jpeg 47 | # - images/a_dot_ham.jpeg 48 | # An image asset can refer to one or more resolution-specific "variants", see 49 | # https://flutter.dev/assets-and-images/#resolution-aware. 50 | # For details regarding adding assets from package dependencies, see 51 | # https://flutter.dev/assets-and-images/#from-packages 52 | # To add custom fonts to your application, add a fonts section here, 53 | # in this "flutter" section. Each entry in this list should have a 54 | # "family" key with the font family name, and a "fonts" key with a 55 | # list giving the asset and other descriptors for the font. For 56 | # example: 57 | # fonts: 58 | # - family: Schyler 59 | # fonts: 60 | # - asset: fonts/Schyler-Regular.ttf 61 | # - asset: fonts/Schyler-Italic.ttf 62 | # style: italic 63 | # - family: Trajan Pro 64 | # fonts: 65 | # - asset: fonts/TrajanPro.ttf 66 | # - asset: fonts/TrajanPro_Bold.ttf 67 | # weight: 700 68 | # 69 | # For details regarding fonts from package dependencies, 70 | # see https://flutter.dev/custom-fonts/#from-packages 71 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_qq_ads_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => 22 | widget is Text && widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module app_store 2 | 3 | go 1.16 4 | 5 | require github.com/flutter/flutter v2.2.3 // indirect 6 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterAds/flutter_qq_ads/6f1cabbd895da8c5a200efc11346931cd251e169/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/Event/FAQAdErrorEvent.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdErrorEvent.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/13. 6 | // 7 | 8 | #import "FAQAdEvent.h" 9 | // 广告错误事件 10 | @interface FAQAdErrorEvent : FAQAdEvent 11 | // 错误码 12 | @property (assign, nonatomic) NSNumber* errCode; 13 | // 错误信息 14 | @property (copy,nonatomic) NSString *errMsg; 15 | // 构造广告错误事件 16 | -(id) initWithAdId:(NSString *)adId errCode:(NSNumber *)errCode errMsg:(NSString *)errMsg; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ios/Classes/Event/FAQAdErrorEvent.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdErrorEvent.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/13. 6 | // 7 | 8 | #import "FAQAdErrorEvent.h" 9 | #import "FAQAdEventAction.h" 10 | 11 | @implementation FAQAdErrorEvent 12 | 13 | - (id)initWithAdId:(NSString *)adId errCode:(NSNumber *)errCode errMsg:(NSString *)errMsg{ 14 | self.adId=adId; 15 | self.action=onAdError; 16 | self.errCode=errCode; 17 | self.errMsg=errMsg; 18 | return self; 19 | } 20 | 21 | - (NSDictionary *)toMap{ 22 | NSDictionary *data=[super toMap]; 23 | NSMutableDictionary *errData=[[NSMutableDictionary alloc]initWithDictionary:data]; 24 | [errData setObject:_errMsg forKey:@"errMsg"]; 25 | [errData setObject:_errCode forKey:@"errCode"]; 26 | 27 | return errData; 28 | } 29 | @end 30 | -------------------------------------------------------------------------------- /ios/Classes/Event/FAQAdEvent.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdEvent.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/13. 6 | // 7 | 8 | #import 9 | // 广告事件 10 | @interface FAQAdEvent : NSObject 11 | // 广告 id 12 | @property (copy, nonatomic) NSString *adId; 13 | // 操作 14 | @property (copy, nonatomic) NSString *action; 15 | // 构造广告事件 16 | - (id) initWithAdId:(NSString *) adId andAction:(NSString *)action; 17 | // 转换为 Map 操作 18 | - (NSDictionary*) toMap; 19 | @end 20 | -------------------------------------------------------------------------------- /ios/Classes/Event/FAQAdEvent.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdEvent.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/13. 6 | // 7 | 8 | #import "FAQAdEvent.h" 9 | 10 | @implementation FAQAdEvent 11 | 12 | - (id)initWithAdId:(NSString *)adId andAction:(NSString *)action{ 13 | self.adId=adId; 14 | self.action=action; 15 | return self; 16 | } 17 | 18 | - (NSDictionary *)toMap{ 19 | NSDictionary *data=@{@"adId":_adId,@"action":_action}; 20 | return data; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /ios/Classes/Event/FAQAdEventAction.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdEventAction.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/14. 6 | // 7 | 8 | #import 9 | // 广告错误 10 | static NSString *const onAdError=@"onAdError"; 11 | // 广告加载成功 12 | static NSString *const onAdLoaded=@"onAdLoaded"; 13 | // 广告填充 14 | static NSString *const onAdPresent=@"onAdPresent"; 15 | // 广告曝光 16 | static NSString *const onAdExposure=@"onAdExposure"; 17 | // 广告关闭(计时结束或者用户点击关闭) 18 | static NSString *const onAdClosed=@"onAdClosed"; 19 | // 广告点击 20 | static NSString *const onAdClicked=@"onAdClicked"; 21 | // 广告跳过 22 | static NSString *const onAdSkip=@"onAdSkip"; 23 | // 广告播放或计时完毕 24 | static NSString *const onAdComplete=@"onAdComplete"; 25 | // 获得广告激励 26 | static NSString *const onAdReward=@"onAdReward"; 27 | // 广告事件操作 28 | @interface FAQAdEventAction : NSObject 29 | @end 30 | -------------------------------------------------------------------------------- /ios/Classes/Event/FAQAdEventAction.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdEventAction.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/14. 6 | // 7 | 8 | #import "FAQAdEventAction.h" 9 | // 广告事件操作 10 | @implementation FAQAdEventAction 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /ios/Classes/Event/FAQAdRewardEvent.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdRewardEvent.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/19. 6 | // 7 | #import "FAQAdEvent.h" 8 | // 广告激励事件 9 | @interface FAQAdRewardEvent : FAQAdEvent 10 | // 服务端验证唯一id 11 | @property (copy,nonatomic) NSString *transId; 12 | // 服务端验证的自定义信息 13 | @property (copy,nonatomic) NSString *customData; 14 | // 服务端验证的用户信息 15 | @property (copy,nonatomic) NSString *userId; 16 | // 构造广告激励事件 17 | -(id) initWithAdId:(NSString*) adId transId:(NSString*) transId customData:(NSString*) customData userId:(NSString*) userId; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ios/Classes/Event/FAQAdRewardEvent.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdRewardEvent.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/19. 6 | // 7 | 8 | #import "FAQAdRewardEvent.h" 9 | #import "FAQAdEventAction.h" 10 | 11 | @implementation FAQAdRewardEvent 12 | - (id)initWithAdId:(NSString *)adId transId:(NSString *)transId customData:(NSString *)customData userId:(NSString *)userId{ 13 | self.adId=adId; 14 | self.action=onAdReward; 15 | self.transId=transId; 16 | self.customData=customData; 17 | self.userId=userId; 18 | return self; 19 | } 20 | 21 | - (NSDictionary *)toMap{ 22 | NSDictionary *data=[super toMap]; 23 | NSMutableDictionary *errData=[[NSMutableDictionary alloc]initWithDictionary:data]; 24 | [errData setObject:_transId forKey:@"transId"]; 25 | [errData setObject:_customData forKey:@"customData"]; 26 | [errData setObject:_userId forKey:@"userId"]; 27 | return errData; 28 | } 29 | @end 30 | -------------------------------------------------------------------------------- /ios/Classes/FlutterQqAdsPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "FAQSplashPage.h" 3 | #import "FAQInterstitialPage.h" 4 | #import "FAQRewardVideoPage.h" 5 | #import "FAQFeedAdLoad.h" 6 | #import "FAQFeedAdManager.h" 7 | 8 | @interface FlutterQqAdsPlugin : NSObject 9 | @property (strong, nonatomic) FlutterEventSink eventSink; 10 | @property (strong, nonatomic) FAQSplashPage *sad; 11 | @property (strong, nonatomic) FAQInterstitialPage *iad; 12 | @property (strong, nonatomic) FAQRewardVideoPage *rvad; 13 | @property (strong, nonatomic) FAQFeedAdLoad *fad; 14 | 15 | extern NSString *const kFAQAdBannerViewId; 16 | extern NSString *const kFAQAdFeedViewId; 17 | @end 18 | -------------------------------------------------------------------------------- /ios/Classes/FlutterQqAdsPlugin.m: -------------------------------------------------------------------------------- 1 | #import "FlutterQqAdsPlugin.h" 2 | #import "GDTSDKConfig.h" 3 | #import "FAQNativeViewFactory.h" 4 | #import 5 | #import 6 | 7 | 8 | @implementation FlutterQqAdsPlugin 9 | 10 | // AdBannerView 11 | NSString *const kFAQAdBannerViewId=@"flutter_qq_ads_banner"; 12 | // AdFeedView 13 | NSString *const kFAQAdFeedViewId=@"flutter_qq_ads_feed"; 14 | 15 | + (void)registerWithRegistrar:(NSObject*)registrar { 16 | // 方法通道 17 | FlutterMethodChannel* methodChannel = [FlutterMethodChannel 18 | methodChannelWithName:@"flutter_qq_ads" 19 | binaryMessenger:[registrar messenger]]; 20 | // 事件通道 21 | FlutterEventChannel* eventChannel=[FlutterEventChannel eventChannelWithName:@"flutter_qq_ads_event" binaryMessenger:[registrar messenger]]; 22 | // 注册方法和事件通道 23 | FlutterQqAdsPlugin* instance = [[FlutterQqAdsPlugin alloc] init]; 24 | [registrar addMethodCallDelegate:instance channel:methodChannel]; 25 | [eventChannel setStreamHandler:instance]; 26 | // 注册平台View 工厂 27 | FAQNativeViewFactory *bannerFactory=[[FAQNativeViewFactory alloc] initWithViewName:kFAQAdBannerViewId withMessenger:registrar.messenger withPlugin:instance]; 28 | FAQNativeViewFactory *feedFactory=[[FAQNativeViewFactory alloc] initWithViewName:kFAQAdFeedViewId withMessenger:registrar.messenger withPlugin:instance]; 29 | // 注册 Banner View 30 | [registrar registerViewFactory:bannerFactory withId:kFAQAdBannerViewId]; 31 | // 注册 Feed View 32 | [registrar registerViewFactory:feedFactory withId:kFAQAdFeedViewId]; 33 | } 34 | 35 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 36 | NSString *methodStr=call.method; 37 | if ([@"getPlatformVersion" isEqualToString:methodStr]) { 38 | result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); 39 | }else if ([@"requestIDFA" isEqualToString:methodStr]) { 40 | [self requestIDFA:call result:result]; 41 | }else if ([@"initAd" isEqualToString:methodStr]) { 42 | [self initAd:call result:result]; 43 | }else if ([@"setPersonalizedState" isEqualToString:methodStr]) { 44 | [self setPersonalizedState:call result:result]; 45 | }else if([@"showSplashAd" isEqualToString:methodStr]) { 46 | [self showSplashAd:call result:result]; 47 | }else if ([@"showInterstitialAd" isEqualToString:methodStr]){ 48 | [self showInterstitialAd:call result:result]; 49 | }else if ([@"showRewardVideoAd" isEqualToString:methodStr]){ 50 | [self showRewardVideoAd:call result:result]; 51 | }else if ([@"loadFeedAd" isEqualToString:methodStr]){ 52 | [self loadFeedAd:call result:result]; 53 | }else if ([@"clearFeedAd" isEqualToString:methodStr]){ 54 | [self clearFeedAd:call result:result]; 55 | }else { 56 | result(FlutterMethodNotImplemented); 57 | } 58 | } 59 | // 请求 IDFA 60 | - (void) requestIDFA:(FlutterMethodCall*) call result:(FlutterResult) result{ 61 | if (@available(iOS 14, *)) { 62 | [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { 63 | BOOL requestResult=status == ATTrackingManagerAuthorizationStatusAuthorized; 64 | NSLog(@"requestIDFA:%@",requestResult?@"YES":@"NO"); 65 | result(@(requestResult)); 66 | }]; 67 | } else { 68 | result(@(YES)); 69 | } 70 | } 71 | 72 | // 初始化广告 73 | - (void) initAd:(FlutterMethodCall*) call result:(FlutterResult) result{ 74 | NSString* appId=call.arguments[@"appId"]; 75 | BOOL initSuccess=[GDTSDKConfig initWithAppId:appId]; 76 | [GDTSDKConfig startWithCompletionHandler:^(BOOL success, NSError *error) { 77 | result(@(success)); 78 | if (success) { 79 | result(@(YES)); 80 | } else { 81 | result(@(NO)); 82 | NSLog(@"FlutterQqAdsPlugin initAd error:%@",error.description); 83 | } 84 | }]; 85 | 86 | } 87 | 88 | // 设置广告个性化 89 | - (void) setPersonalizedState:(FlutterMethodCall*) call result:(FlutterResult) result{ 90 | int state = [call.arguments[@"state"] intValue]; 91 | [GDTSDKConfig setPersonalizedState:state]; 92 | result(@(YES)); 93 | } 94 | 95 | // 显示开屏广告 96 | - (void) showSplashAd:(FlutterMethodCall*) call result:(FlutterResult) result{ 97 | self.sad=[[FAQSplashPage alloc] init]; 98 | [self.sad showAd:call eventSink:self.eventSink]; 99 | result(@(YES)); 100 | } 101 | 102 | // 显示插屏广告 103 | - (void) showInterstitialAd:(FlutterMethodCall*) call result:(FlutterResult) result{ 104 | self.iad=[[FAQInterstitialPage alloc] init]; 105 | [self.iad showAd:call eventSink:self.eventSink]; 106 | result(@(YES)); 107 | } 108 | 109 | // 显示激励视频广告 110 | - (void) showRewardVideoAd:(FlutterMethodCall*) call result:(FlutterResult) result{ 111 | self.rvad=[[FAQRewardVideoPage alloc] init]; 112 | [self.rvad showAd:call eventSink:self.eventSink]; 113 | result(@(YES)); 114 | } 115 | 116 | // 加载信息流广告 117 | - (void) loadFeedAd:(FlutterMethodCall*) call result:(FlutterResult) result{ 118 | self.fad=[[FAQFeedAdLoad alloc] init]; 119 | [self.fad loadFeedAdList:call result:result eventSink:self.eventSink]; 120 | } 121 | 122 | // 清除信息流广告 123 | - (void) clearFeedAd:(FlutterMethodCall*) call result:(FlutterResult) result{ 124 | NSArray *list= call.arguments[@"list"]; 125 | for (NSNumber *ad in list) { 126 | [FAQFeedAdManager.share removeAd:ad]; 127 | } 128 | result(@(YES)); 129 | } 130 | 131 | 132 | #pragma mark - FlutterStreamHandler 133 | - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { 134 | self.eventSink=nil; 135 | return nil; 136 | } 137 | 138 | - (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { 139 | self.eventSink=events; 140 | return nil; 141 | } 142 | 143 | // 添加事件 144 | -(void) addEvent:(NSObject *) event{ 145 | if(self.eventSink!=nil){ 146 | self.eventSink(event); 147 | } 148 | } 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /ios/Classes/Load/FAQFeedAdLoad.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQFeedAdLoad.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/12/4. 6 | // 7 | 8 | #import 9 | #import "FAQBaseAdPage.h" 10 | 11 | @interface FAQFeedAdLoad :FAQBaseAdPage 12 | @property (strong,nonatomic,nonnull) FlutterResult result; 13 | // 加载信息流广告列表 14 | -(void) loadFeedAdList:(nonnull FlutterMethodCall *)call result:(nonnull FlutterResult) result eventSink:(nonnull FlutterEventSink )events; 15 | @end 16 | -------------------------------------------------------------------------------- /ios/Classes/Load/FAQFeedAdLoad.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQFeedAdLoad.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/12/4. 6 | // 7 | 8 | #import "FAQFeedAdLoad.h" 9 | #import "FAQFeedAdManager.h" 10 | #import "FlutterQqAdsPlugin.h" 11 | #import "GDTNativeExpressAd.h" 12 | #import "GDTNativeExpressAdView.h" 13 | 14 | @interface FAQFeedAdLoad() 15 | @property (strong,nonatomic) GDTNativeExpressAd *ad; 16 | 17 | @end 18 | 19 | @implementation FAQFeedAdLoad 20 | 21 | - (void)loadFeedAdList:(FlutterMethodCall *)call result:(FlutterResult)result eventSink:(FlutterEventSink)events{ 22 | self.result=result; 23 | [self showAd:call eventSink:events]; 24 | } 25 | 26 | - (void)loadAd:(FlutterMethodCall *)call{ 27 | int width = [call.arguments[@"width"] intValue]; 28 | int height = [call.arguments[@"height"] intValue]; 29 | int count = [call.arguments[@"count"] intValue]; 30 | 31 | CGSize size= CGSizeMake(width, height); 32 | 33 | self.ad=[[GDTNativeExpressAd alloc] initWithPlacementId:self.posId adSize:size]; 34 | self.ad.delegate=self; 35 | [self.ad loadAd:count]; 36 | } 37 | 38 | #pragma mark - GDTNativeExpressAdDelegete 39 | 40 | - (void)nativeExpressAdSuccessToLoad:(GDTNativeExpressAd *)nativeExpressAd views:(NSArray<__kindof GDTNativeExpressAdView *> *)views{ 41 | NSLog(@"%s",__FUNCTION__); 42 | if (views.count) { 43 | // 广告列表,用于返回 Flutter 层 44 | NSMutableArray *adList= [[NSMutableArray alloc] init]; 45 | [views enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 46 | // 通过hash 来标识不同的原生广告 View 47 | NSNumber *key=[NSNumber numberWithInteger:[obj hash]]; 48 | NSLog(@"FeedAdLoad idx:%lu obj:%p hash:%@",(unsigned long)[obj hash],obj,key); 49 | // 添加到返回列表中 50 | [adList addObject:key]; 51 | // 添加到缓存广告列表中 52 | [FAQFeedAdManager.share putAd:key value:obj]; 53 | }]; 54 | // 返回广告列表 55 | self.result(adList); 56 | } 57 | // 发送广告事件 58 | [self sendEventAction:onAdLoaded]; 59 | } 60 | 61 | - (void)nativeExpressAdFailToLoad:(GDTNativeExpressAd *)nativeExpressAd error:(NSError *)error{ 62 | NSLog(@"%s error:%@",__FUNCTION__,error); 63 | self.result(@[]); 64 | } 65 | 66 | - (void)nativeExpressAdViewRenderSuccess:(GDTNativeExpressAdView *)nativeExpressAdView{ 67 | NSLog(@"%s",__FUNCTION__); 68 | // 发送广告事件 69 | [self sendEventAction:onAdPresent]; 70 | // 渲染成功,发送更新展示通知,来更新尺寸 71 | [self postNotificationMsg:nativeExpressAdView userInfo:[NSDictionary dictionaryWithObject:onAdPresent forKey:@"event"]]; 72 | } 73 | 74 | - (void)nativeExpressAdViewRenderFail:(GDTNativeExpressAdView *)nativeExpressAdView{ 75 | NSLog(@"%s",__FUNCTION__); 76 | // 渲染成功,发送更新展示通知,来更新尺寸 77 | [self postNotificationMsg:nativeExpressAdView userInfo:[NSDictionary dictionaryWithObject:onAdError forKey:@"event"]]; 78 | // 发送广告错误事件 79 | [self sendErrorEvent:-100 withErrMsg:@"ExpressAdViewRenderFail"]; 80 | } 81 | 82 | - (void)nativeExpressAdViewExposure:(GDTNativeExpressAdView *)nativeExpressAdView{ 83 | NSLog(@"%s",__FUNCTION__); 84 | // 发送广告事件 85 | [self sendEventAction:onAdExposure]; 86 | } 87 | 88 | - (void)nativeExpressAdViewClicked:(GDTNativeExpressAdView *)nativeExpressAdView{ 89 | NSLog(@"%s",__FUNCTION__); 90 | // 发送广告事件 91 | [self sendEventAction:onAdClicked]; 92 | } 93 | 94 | - (void)nativeExpressAdViewClosed:(GDTNativeExpressAdView *)nativeExpressAdView{ 95 | NSLog(@"%s",__FUNCTION__); 96 | NSNumber *key=[NSNumber numberWithInteger:[nativeExpressAdView hash]]; 97 | // 删除广告缓存 98 | [FAQFeedAdManager.share removeAd:key]; 99 | // 发送关闭消息 100 | [self postNotificationMsg:nativeExpressAdView userInfo:[NSDictionary dictionaryWithObject:onAdClosed forKey:@"event"]]; 101 | // 发送广告事件 102 | [self sendEventAction:onAdClosed]; 103 | } 104 | 105 | // 发送消息 106 | // 这里发送消息到信息流View,主要是适配信息流 View 的尺寸 107 | - (void) postNotificationMsg:(GDTNativeExpressAdView *) adView userInfo:(NSDictionary *) userInfo{ 108 | NSLog(@"%s",__FUNCTION__); 109 | NSNumber *key=[NSNumber numberWithInteger:[adView hash]]; 110 | NSString *name=[NSString stringWithFormat:@"%@/%@", kFAQAdFeedViewId, key.stringValue]; 111 | [[NSNotificationCenter defaultCenter] postNotificationName:name object:adView userInfo:userInfo]; 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /ios/Classes/Load/FAQFeedAdManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQFeedAdManager.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/12/4. 6 | // 7 | 8 | #import 9 | // 信息流广告管理 10 | @interface FAQFeedAdManager : NSObject 11 | // 单利类方法 12 | + (instancetype) share; 13 | // 加入到缓存中 14 | - (void) putAd:(NSNumber *) key value:(id) value; 15 | // 从缓存中获取 16 | - (id) getAd:(NSNumber *) key; 17 | // 从缓存中删除 18 | - (void) removeAd:(NSNumber *) key; 19 | @end 20 | -------------------------------------------------------------------------------- /ios/Classes/Load/FAQFeedAdManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQFeedAdManager.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/12/4. 6 | // 7 | 8 | #import "FAQFeedAdManager.h" 9 | 10 | @implementation FAQFeedAdManager 11 | 12 | static FAQFeedAdManager *adManager;// 广告管理实例 13 | NSMutableDictionary *faqAdList;// 已加载信息流广告列表 14 | 15 | + (instancetype)share{ 16 | static dispatch_once_t once_token; 17 | dispatch_once(&once_token, ^{ 18 | faqAdList = [[NSMutableDictionary alloc] init]; 19 | adManager=[[FAQFeedAdManager alloc] init]; 20 | }); 21 | return adManager; 22 | } 23 | 24 | - (void)putAd:(NSNumber *)key value:(id)value{ 25 | [faqAdList setObject:value forKey:key]; 26 | } 27 | 28 | - (id)getAd:(NSNumber *)key{ 29 | return [faqAdList objectForKey:key]; 30 | } 31 | 32 | - (void)removeAd:(NSNumber *)key{ 33 | [faqAdList removeObjectForKey:key]; 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQAdBannerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdBannerView.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/31. 6 | // 7 | 8 | #import "FAQBaseAdPage.h" 9 | #import "FlutterQqAdsPlugin.h" 10 | @interface FAQAdBannerView : FAQBaseAdPage 11 | @property (strong,nonatomic,nonnull) FlutterQqAdsPlugin *plugin; 12 | - (nonnull instancetype)initWithFrame:(CGRect)frame 13 | viewIdentifier:(int64_t)viewId 14 | arguments:(id _Nullable)args 15 | binaryMessenger:(NSObject* _Nullable)messenger plugin:(FlutterQqAdsPlugin* _Nullable) plugin; 16 | 17 | - (nonnull UIView*)view; 18 | @end 19 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQAdBannerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdBannerView.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/31. 6 | // 7 | 8 | #import "FAQAdBannerView.h" 9 | #import "GDTUnifiedBannerView.h" 10 | // Banner 广告 View 11 | @interface FAQAdBannerView() 12 | @property (strong,nonatomic) GDTUnifiedBannerView *bannerView; 13 | @end 14 | // Banner 广告 View 15 | @implementation FAQAdBannerView 16 | - (instancetype)initWithFrame:(CGRect)frame 17 | viewIdentifier:(int64_t)viewId 18 | arguments:(id _Nullable)args 19 | binaryMessenger:(NSObject*)messenger 20 | plugin:(FlutterQqAdsPlugin*) plugin{ 21 | if (self = [super init]) { 22 | FlutterMethodCall *call=[FlutterMethodCall methodCallWithMethodName:@"FAQAdBannerView" arguments:args]; 23 | [self showAd:call eventSink:plugin.eventSink]; 24 | } 25 | return self; 26 | } 27 | 28 | - (UIView*)view { 29 | return self.bannerView; 30 | } 31 | // 加载广告 32 | - (void)loadAd:(FlutterMethodCall *)call{ 33 | // 刷新间隔 34 | int interval=[call.arguments[@"interval"] intValue]; 35 | // 创建 Banner 36 | self.bannerView=[[GDTUnifiedBannerView alloc] initWithPlacementId:self.posId viewController:self.rootController]; 37 | // 设置 Delegate 38 | self.bannerView.delegate=self; 39 | // 这里处理如果刷新间隔不为 0,则展示动画 40 | self.bannerView.animated=interval!=0; 41 | // 设置刷新间隔 42 | self.bannerView.autoSwitchInterval=interval; 43 | // 加载动画 44 | [self.bannerView loadAdAndShow]; 45 | } 46 | 47 | 48 | #pragma mark - GDTUnifiedBannerViewDelegate 49 | /** 50 | * 请求广告条数据成功后调用 51 | * 当接收服务器返回的广告数据成功后调用该函数 52 | */ 53 | - (void)unifiedBannerViewDidLoad:(GDTUnifiedBannerView *)unifiedBannerView 54 | { 55 | NSLog(@"%s",__FUNCTION__); 56 | // 发送广告事件 57 | [self sendEventAction:onAdLoaded]; 58 | } 59 | 60 | /** 61 | * 请求广告条数据失败后调用 62 | * 当接收服务器返回的广告数据失败后调用该函数 63 | */ 64 | 65 | - (void)unifiedBannerViewFailedToLoad:(GDTUnifiedBannerView *)unifiedBannerView error:(NSError *)error 66 | { 67 | self.bannerView = nil; 68 | NSLog(@"%s%@",__FUNCTION__,error); 69 | // 发送广告错误事件 70 | [self sendErrorEvent:error.code withErrMsg:error.localizedDescription]; 71 | } 72 | 73 | /** 74 | * banner2.0曝光回调 75 | */ 76 | - (void)unifiedBannerViewWillExpose:(nonnull GDTUnifiedBannerView *)unifiedBannerView { 77 | NSLog(@"%s",__FUNCTION__); 78 | // 发送广告事件 79 | [self sendEventAction:onAdExposure]; 80 | } 81 | 82 | /** 83 | * banner2.0点击回调 84 | */ 85 | - (void)unifiedBannerViewClicked:(GDTUnifiedBannerView *)unifiedBannerView 86 | { 87 | NSLog(@"%s",__FUNCTION__); 88 | // 发送广告事件 89 | [self sendEventAction:onAdClicked]; 90 | } 91 | 92 | /** 93 | * 应用进入后台时调用 94 | * 当点击应用下载或者广告调用系统程序打开,应用将被自动切换到后台 95 | */ 96 | - (void)unifiedBannerViewWillLeaveApplication:(GDTUnifiedBannerView *)unifiedBannerView 97 | { 98 | NSLog(@"%s",__FUNCTION__); 99 | } 100 | 101 | /** 102 | * 全屏广告页已经被关闭 103 | */ 104 | - (void)unifiedBannerViewDidDismissFullScreenModal:(GDTUnifiedBannerView *)unifiedBannerView 105 | { 106 | NSLog(@"%s",__FUNCTION__); 107 | } 108 | 109 | /** 110 | * 全屏广告页即将被关闭 111 | */ 112 | - (void)unifiedBannerViewWillDismissFullScreenModal:(GDTUnifiedBannerView *)unifiedBannerView 113 | { 114 | NSLog(@"%s",__FUNCTION__); 115 | } 116 | 117 | /** 118 | * banner2.0广告点击以后即将弹出全屏广告页 119 | */ 120 | - (void)unifiedBannerViewWillPresentFullScreenModal:(GDTUnifiedBannerView *)unifiedBannerView 121 | { 122 | NSLog(@"%s",__FUNCTION__); 123 | } 124 | 125 | /** 126 | * banner2.0广告点击以后弹出全屏广告页完毕 127 | */ 128 | - (void)unifiedBannerViewDidPresentFullScreenModal:(GDTUnifiedBannerView *)unifiedBannerView 129 | { 130 | NSLog(@"%s",__FUNCTION__); 131 | } 132 | 133 | /** 134 | * banner2.0被用户关闭时调用 135 | */ 136 | - (void)unifiedBannerViewWillClose:(nonnull GDTUnifiedBannerView *)unifiedBannerView { 137 | self.bannerView = nil; 138 | NSLog(@"%s",__FUNCTION__); 139 | // 发送广告事件 140 | [self sendEventAction:onAdClosed]; 141 | } 142 | @end 143 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQAdFeedView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdFeedView.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/12/4. 6 | // 7 | #import "FAQBaseAdPage.h" 8 | #import "FlutterQqAdsPlugin.h" 9 | // 信息流广告 View 10 | @interface FAQAdFeedView : FAQBaseAdPage 11 | @property (strong,nonatomic,nullable) FlutterQqAdsPlugin *plugin; 12 | @property int64_t viewId; 13 | - (nonnull instancetype) initWithFrame:(CGRect)frame 14 | viewIdentifier:(int64_t)viewId 15 | arguments:(id _Nullable)args 16 | binaryMessenger:(NSObject* _Nullable)messenger plugin:(FlutterQqAdsPlugin* _Nullable) plugin; 17 | - (nonnull UIView*) view; 18 | @end 19 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQAdFeedView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQAdFeedView.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/12/4. 6 | // 7 | #import "FAQAdFeedView.h" 8 | #import "FAQFeedAdManager.h" 9 | #import "GDTNativeExpressAd.h" 10 | #import "GDTNativeExpressAdView.h" 11 | 12 | @interface FAQAdFeedView() 13 | @property (strong,nonatomic) UIView *feedView; 14 | @property (strong,nonatomic) GDTNativeExpressAdView *adView; 15 | @property (strong,nonatomic) FlutterMethodChannel *methodChannel; 16 | 17 | @end 18 | 19 | @implementation FAQAdFeedView 20 | 21 | - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args binaryMessenger:(NSObject *)messenger plugin:(FlutterQqAdsPlugin *)plugin{ 22 | if(self==[super init]){ 23 | self.viewId=viewId; 24 | self.feedView =[[UIView alloc] init]; 25 | self.methodChannel = [FlutterMethodChannel methodChannelWithName:[NSString stringWithFormat:@"%@/%lli",kFAQAdFeedViewId,viewId] binaryMessenger:messenger]; 26 | FlutterMethodCall *call= [FlutterMethodCall methodCallWithMethodName:@"AdFeedView" arguments:args]; 27 | [self showAd:call eventSink:plugin.eventSink]; 28 | } 29 | NSLog(@"%s %lli",__FUNCTION__,viewId); 30 | return self; 31 | } 32 | 33 | - (UIView *)view{ 34 | return self.feedView; 35 | } 36 | 37 | - (void)dealloc{ 38 | NSLog(@"%s",__FUNCTION__); 39 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 40 | } 41 | 42 | // 处理消息 43 | - (void) postMsghandler:(NSNotification*) notification{ 44 | NSString *name=notification.name; 45 | GDTNativeExpressAdView *loadAdView=notification.object; 46 | NSDictionary *userInfo=notification.userInfo; 47 | NSString *event=[userInfo objectForKey:@"event"]; 48 | NSLog(@"%s postMsghandler name:%@ userInfo:%@",__FUNCTION__,name,userInfo); 49 | if([event isEqualToString:onAdPresent]){ 50 | // 设置 Feed View 的大小与广告View 的实际大小一致 51 | self.feedView.frame=self.adView.frame; 52 | // 渲染成功,设置高度 53 | CGSize size= loadAdView.bounds.size; 54 | [self setFlutterViewSize:size]; 55 | }else if([event isEqualToString:onAdClosed]||[event isEqualToString:onAdError]){ 56 | // 关闭移除广告或出错,则设置大小为 0,隐藏广告 57 | [self.adView removeFromSuperview]; 58 | [self setFlutterViewSize:CGSizeZero]; 59 | } 60 | } 61 | // 设置 FlutterAds 视图宽高 62 | - (void) setFlutterViewSize:(CGSize) size{ 63 | NSNumber *width=[NSNumber numberWithFloat:size.width]; 64 | NSNumber *height=[NSNumber numberWithFloat:size.height]; 65 | NSDictionary *dicSize=[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:width,height, nil] forKeys:[NSArray arrayWithObjects:@"width",@"height", nil]]; 66 | // 通知 Flutter View 设置大小 67 | [self.methodChannel invokeMethod:@"setSize" arguments:dicSize]; 68 | } 69 | 70 | - (void)loadAd:(FlutterMethodCall *)call{ 71 | NSNumber *key=[NSNumber numberWithInteger:[self.posId integerValue]]; 72 | NSString *name=[NSString stringWithFormat:@"%@/%@", kFAQAdFeedViewId, key.stringValue]; 73 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(postMsghandler:) name:name object:nil]; 74 | self.adView=[FAQFeedAdManager.share getAd:key]; 75 | if(self.adView){ 76 | self.adView.controller=self.rootController; 77 | [self.feedView addSubview:self.adView]; 78 | [self.adView render]; 79 | } 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQBaseAdPage.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQBaseAdPage.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/18. 6 | // 7 | #import 8 | #import 9 | #import "FAQAdEvent.h" 10 | #import "FAQAdErrorEvent.h" 11 | #import "FAQAdRewardEvent.h" 12 | #import "FAQAdEventAction.h" 13 | // 广告位id 14 | static NSString * _Nonnull const kPosId=@"posId"; 15 | 16 | // 基础广告页面 17 | @interface FAQBaseAdPage : NSObject 18 | // 广告位id 19 | @property (weak,nonatomic,nullable) NSString *posId; 20 | // 事件消息 21 | @property (strong, nonatomic,nonnull) FlutterEventSink eventSink; 22 | // Window 23 | @property (strong,nonatomic,nonnull) UIWindow *mainWin; 24 | // 根控制器 25 | @property (strong,nonatomic,nonnull) UIViewController *rootController; 26 | // 屏幕宽度 27 | @property CGFloat width; 28 | // 屏幕高度 29 | @property CGFloat height; 30 | // 显示广告 31 | - (void) showAd:(nonnull FlutterMethodCall *)call eventSink:(nonnull FlutterEventSink) events; 32 | // 加载广告 33 | - (void) loadAd:(nonnull FlutterMethodCall *) call; 34 | // 发送广告事件 35 | -(void) sendEvent:(nonnull FAQAdEvent *) event; 36 | // 发送广告事件 37 | -(void) sendEventAction:(nonnull NSString *) action; 38 | // 发送广告错误事件 39 | -(void) sendErrorEvent:(NSInteger) errCode withErrMsg:(nonnull NSString*) errMsg; 40 | @end 41 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQBaseAdPage.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQBaseAdPage.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/18. 6 | // 7 | 8 | #import "FAQBaseAdPage.h" 9 | 10 | @implementation FAQBaseAdPage 11 | // 添加广告事件 12 | -(void) addAdEvent:(FAQAdEvent *) event{ 13 | if(self.eventSink!=nil){ 14 | self.eventSink([event toMap]); 15 | } 16 | } 17 | // 显示广告 18 | -(void)showAd:(FlutterMethodCall *)call eventSink:(nonnull FlutterEventSink )events{ 19 | self.posId=call.arguments[kPosId]; 20 | self.eventSink=events; 21 | // 获取主 window 22 | self.mainWin=[[UIApplication sharedApplication] keyWindow]; 23 | self.rootController=self.mainWin.rootViewController; 24 | // 获取宽高 25 | CGSize size=[[UIScreen mainScreen] bounds].size; 26 | self.width=size.width; 27 | self.height=size.height; 28 | [self loadAd:call]; 29 | } 30 | - (void)loadAd:(FlutterMethodCall *)call{ 31 | NSLog(@"%s",__FUNCTION__); 32 | } 33 | // 发送广告事件 34 | - (void)sendEvent:(FAQAdEvent *)event{ 35 | if(self.eventSink!=nil){ 36 | self.eventSink([event toMap]); 37 | } 38 | } 39 | // 发送广告事件 40 | - (void)sendEventAction:(NSString *)action{ 41 | FAQAdEvent *event=[[FAQAdEvent alloc] initWithAdId:self.posId andAction:action]; 42 | [self sendEvent:event]; 43 | } 44 | // 发送广告错误事件 45 | - (void)sendErrorEvent:(NSInteger)errCode withErrMsg:(NSString *)errMsg{ 46 | FAQAdErrorEvent *event=[[FAQAdErrorEvent alloc] initWithAdId:self.posId errCode:[NSNumber numberWithInteger:errCode] errMsg:errMsg]; 47 | [self sendEvent:event]; 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQInterstitialPage.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQInterstitialPage.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/18. 6 | // 7 | 8 | #import "FAQBaseAdPage.h" 9 | // 插屏广告 10 | @interface FAQInterstitialPage : FAQBaseAdPage 11 | @end 12 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQInterstitialPage.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQInterstitialPage.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/18. 6 | // 7 | 8 | #import "FAQInterstitialPage.h" 9 | #import "GDTUnifiedInterstitialAd.h" 10 | 11 | // 插屏广告 12 | @interface FAQInterstitialPage() 13 | @property (nonatomic, strong) GDTUnifiedInterstitialAd *iad; 14 | // 全屏视频形式展示 15 | @property BOOL showFullScreenVideo; 16 | // 激励视频形式展示 17 | @property BOOL showRewardVideo; 18 | // 服务端验证的自定义信息 19 | @property (copy,nonatomic) NSString *customData; 20 | // 服务端验证的用户信息 21 | @property (copy,nonatomic) NSString *userId; 22 | @end 23 | 24 | @implementation FAQInterstitialPage 25 | 26 | - (void)dealloc 27 | { 28 | NSLog(@"InterstitialPage dealloc"); 29 | } 30 | // 加载广告 31 | - (void)loadAd:(FlutterMethodCall *)call{ 32 | NSLog(@"加载广告:%@",self.posId); 33 | 34 | 35 | self.iad=[[GDTUnifiedInterstitialAd alloc] initWithPlacementId:self.posId]; 36 | self.iad.delegate=self; 37 | [self setOption:call]; 38 | // 插屏全屏视频或插屏激励视频加载方式 39 | if (self.showFullScreenVideo||self.showRewardVideo) { 40 | [self.iad loadFullScreenAd]; 41 | } else { 42 | [self.iad loadAd]; 43 | } 44 | 45 | } 46 | // 配置项 47 | - (void) setOption:(FlutterMethodCall *) call{ 48 | self.showFullScreenVideo = [call.arguments[@"showFullScreenVideo"] boolValue]; 49 | self.showRewardVideo= [call.arguments[@"showRewardVideo"] boolValue]; 50 | BOOL autoPlayMuted = [call.arguments[@"autoPlayMuted"] boolValue]; 51 | BOOL autoPlayOnWifi = [call.arguments[@"autoPlayOnWifi"] boolValue]; 52 | self.iad.videoAutoPlayOnWWAN=autoPlayOnWifi; 53 | self.iad.videoMuted=autoPlayMuted; 54 | // 激励视频配置项 55 | if (self.showRewardVideo) { 56 | self.customData = call.arguments[@"customData"] ; 57 | self.userId = call.arguments[@"userId"]; 58 | //如果设置了服务端验证,可以设置serverSideVerificationOptions属性 59 | GDTServerSideVerificationOptions *ssv = [[GDTServerSideVerificationOptions alloc] init]; 60 | ssv.userIdentifier = self.userId; 61 | ssv.customRewardString = self.customData; 62 | self.iad.serverSideVerificationOptions = ssv; 63 | } 64 | } 65 | 66 | #pragma mark - GDTUnifiedInterstitialAdDelegate 67 | 68 | /** 69 | * 插屏2.0广告预加载成功回调 70 | * 当接收服务器返回的广告数据成功后调用该函数 71 | */ 72 | - (void)unifiedInterstitialSuccessToLoadAd:(GDTUnifiedInterstitialAd *)unifiedInterstitial 73 | { 74 | NSLog(@"%s",__FUNCTION__); 75 | // 发送广告事件 76 | [self sendEventAction:onAdLoaded]; 77 | } 78 | 79 | /** 80 | * 插屏2.0广告预加载失败回调 81 | * 当接收服务器返回的广告数据失败后调用该函数 82 | */ 83 | - (void)unifiedInterstitialFailToLoadAd:(GDTUnifiedInterstitialAd *)unifiedInterstitial error:(NSError *)error 84 | { 85 | NSLog(@"%s",__FUNCTION__); 86 | NSLog(@"interstitial fail to load, Error : %@",error); 87 | // 发送广告错误事件 88 | [self sendErrorEvent:error.code withErrMsg:error.localizedDescription]; 89 | } 90 | 91 | 92 | - (void)unifiedInterstitialDidDownloadVideo:(GDTUnifiedInterstitialAd *)unifiedInterstitial { 93 | NSLog(@"%s",__FUNCTION__); 94 | } 95 | 96 | - (void)unifiedInterstitialRenderSuccess:(GDTUnifiedInterstitialAd *)unifiedInterstitial { 97 | NSLog(@"%s",__FUNCTION__); 98 | UIViewController* controller = [UIApplication sharedApplication].keyWindow.rootViewController; 99 | if (self.showFullScreenVideo||self.showRewardVideo) { 100 | [self.iad presentFullScreenAdFromRootViewController:controller]; 101 | }else{ 102 | [self.iad presentAdFromRootViewController:controller]; 103 | } 104 | 105 | } 106 | 107 | - (void)unifiedInterstitialRenderFail:(GDTUnifiedInterstitialAd *)unifiedInterstitial error:(NSError *)error { 108 | NSLog(@"%s",__FUNCTION__); 109 | // 添加广告错误事件 110 | FAQAdErrorEvent *event=[[FAQAdErrorEvent alloc] initWithAdId:self.posId errCode:[NSNumber numberWithInteger:error.code] errMsg:error.localizedDescription]; 111 | [self sendEvent:event]; 112 | } 113 | 114 | /** 115 | * 插屏2.0广告将要展示回调 116 | * 插屏2.0广告即将展示回调该函数 117 | */ 118 | - (void)unifiedInterstitialWillPresentScreen:(GDTUnifiedInterstitialAd *)unifiedInterstitial 119 | { 120 | NSLog(@"%s",__FUNCTION__); 121 | } 122 | 123 | - (void)unifiedInterstitialFailToPresent:(GDTUnifiedInterstitialAd *)unifiedInterstitial error:(NSError *)error { 124 | NSLog(@"%s",__FUNCTION__); 125 | } 126 | 127 | /** 128 | * 插屏2.0广告视图展示成功回调 129 | * 插屏2.0广告展示成功回调该函数 130 | */ 131 | - (void)unifiedInterstitialDidPresentScreen:(GDTUnifiedInterstitialAd *)unifiedInterstitial 132 | { 133 | NSLog(@"%s",__FUNCTION__); 134 | // 发送广告事件 135 | [self sendEventAction:onAdPresent]; 136 | } 137 | 138 | /** 139 | * 插屏2.0广告展示结束回调 140 | * 插屏2.0广告展示结束回调该函数 141 | */ 142 | - (void)unifiedInterstitialDidDismissScreen:(GDTUnifiedInterstitialAd *)unifiedInterstitial 143 | { 144 | NSLog(@"%s",__FUNCTION__); 145 | // 发送广告事件 146 | [self sendEventAction:onAdClosed]; 147 | } 148 | 149 | /** 150 | * 当点击下载应用时会调用系统程序打开 151 | */ 152 | - (void)unifiedInterstitialWillLeaveApplication:(GDTUnifiedInterstitialAd *)unifiedInterstitial 153 | { 154 | NSLog(@"%s",__FUNCTION__); 155 | } 156 | 157 | /** 158 | * 插屏2.0广告曝光回调 159 | */ 160 | - (void)unifiedInterstitialWillExposure:(GDTUnifiedInterstitialAd *)unifiedInterstitial 161 | { 162 | NSLog(@"%s",__FUNCTION__); 163 | // 发送广告事件 164 | [self sendEventAction:onAdExposure]; 165 | } 166 | 167 | /** 168 | * 插屏2.0广告点击回调 169 | */ 170 | - (void)unifiedInterstitialClicked:(GDTUnifiedInterstitialAd *)unifiedInterstitial 171 | { 172 | NSLog(@"%s",__FUNCTION__); 173 | // 发送广告事件 174 | [self sendEventAction:onAdClicked]; 175 | } 176 | 177 | /** 178 | * 点击插屏2.0广告以后即将弹出全屏广告页 179 | */ 180 | - (void)unifiedInterstitialAdWillPresentFullScreenModal:(GDTUnifiedInterstitialAd *)unifiedInterstitial 181 | { 182 | NSLog(@"%s",__FUNCTION__); 183 | } 184 | 185 | /** 186 | * 点击插屏2.0广告以后弹出全屏广告页 187 | */ 188 | - (void)unifiedInterstitialAdDidPresentFullScreenModal:(GDTUnifiedInterstitialAd *)unifiedInterstitial 189 | { 190 | NSLog(@"%s",__FUNCTION__); 191 | } 192 | 193 | /** 194 | * 全屏广告页将要关闭 195 | */ 196 | - (void)unifiedInterstitialAdWillDismissFullScreenModal:(GDTUnifiedInterstitialAd *)unifiedInterstitial 197 | { 198 | NSLog(@"%s",__FUNCTION__); 199 | } 200 | 201 | /** 202 | * 全屏广告页被关闭 203 | */ 204 | - (void)unifiedInterstitialAdDidDismissFullScreenModal:(GDTUnifiedInterstitialAd *)unifiedInterstitial 205 | { 206 | NSLog(@"%s",__FUNCTION__); 207 | } 208 | 209 | - (void)unifiedInterstitialAdDidRewardEffective:(GDTUnifiedInterstitialAd *)unifiedInterstitial info:(NSDictionary *)info { 210 | NSString *transId=[info objectForKey:@"GDT_TRANS_ID"]; 211 | NSLog(@"播放达到激励条件 transid:%@", transId); 212 | // 发送激励事件 213 | FAQAdRewardEvent *event=[[FAQAdRewardEvent alloc] initWithAdId:self.posId transId:transId customData:self.customData userId:self.userId]; 214 | [self sendEvent:event]; 215 | } 216 | 217 | @end 218 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQNativeViewFactory.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQNativeViewFactory.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/31. 6 | // 7 | #import 8 | #import 9 | #import "FAQAdBannerView.h" 10 | #import "FAQAdFeedView.h" 11 | 12 | // 原生平台 View 工厂 13 | @interface FAQNativeViewFactory : NSObject 14 | @property (strong,nonatomic) NSObject *messenger; 15 | @property (strong,nonatomic) FlutterQqAdsPlugin *plugin; 16 | @property (strong,nonatomic) NSString *viewName; 17 | - (instancetype)initWithViewName:(NSString*) viewName withMessenger:(NSObject*)messenger withPlugin:(FlutterQqAdsPlugin*) plugin; 18 | @end 19 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQNativeViewFactory.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQNativeViewFactory.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/31. 6 | // 7 | 8 | #import "FAQNativeViewFactory.h" 9 | // 原生平台 View 工厂 10 | @implementation FAQNativeViewFactory 11 | 12 | - (instancetype)initWithViewName:(NSString *)viewName withMessenger:(NSObject *)messenger withPlugin:(FlutterQqAdsPlugin *)plugin{ 13 | self = [super init]; 14 | if (self) { 15 | self.viewName = viewName; 16 | self.messenger = messenger; 17 | self.plugin = plugin; 18 | } 19 | return self; 20 | } 21 | 22 | - (NSObject*)createArgsCodec { 23 | return [FlutterStandardMessageCodec sharedInstance]; 24 | } 25 | 26 | - (NSObject*)createWithFrame:(CGRect)frame 27 | viewIdentifier:(int64_t)viewId 28 | arguments:(id _Nullable)args { 29 | if(self.viewName==kFAQAdBannerViewId){ 30 | return [[FAQAdBannerView alloc] initWithFrame:frame 31 | viewIdentifier:viewId 32 | arguments:args 33 | binaryMessenger:self.messenger 34 | plugin:self.plugin]; 35 | 36 | }else{ 37 | return [[FAQAdFeedView alloc] initWithFrame:frame 38 | viewIdentifier:viewId 39 | arguments:args 40 | binaryMessenger:self.messenger 41 | plugin:self.plugin]; 42 | } 43 | } 44 | @end 45 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQRewardVideoPage.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQRewardVideoPage.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/19. 6 | // 7 | 8 | #import "FAQBaseAdPage.h" 9 | // 激励视频页面 10 | @interface FAQRewardVideoPage : FAQBaseAdPage 11 | @end 12 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQRewardVideoPage.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQRewardVideoPage.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/19. 6 | // 7 | 8 | #import "FAQRewardVideoPage.h" 9 | #import "GDTRewardVideoAd.h" 10 | // 激励视频页面 11 | @interface FAQRewardVideoPage() 12 | @property (nonatomic, strong) GDTRewardVideoAd *rvad; 13 | // 服务端验证的自定义信息 14 | @property (copy,nonatomic) NSString *customData; 15 | // 服务端验证的用户信息 16 | @property (copy,nonatomic) NSString *userId; 17 | @end 18 | 19 | @implementation FAQRewardVideoPage 20 | // 加载广告 21 | - (void)loadAd:(FlutterMethodCall *)call{ 22 | BOOL playMuted=[call.arguments[@"playMuted"] boolValue]; 23 | self.customData = call.arguments[@"customData"] ; 24 | self.userId = call.arguments[@"userId"]; 25 | // 初始化激励视频广告 26 | self.rvad= [[GDTRewardVideoAd alloc] initWithPlacementId:self.posId]; 27 | self.rvad.delegate=self; 28 | self.rvad.videoMuted=playMuted; 29 | //如果设置了服务端验证,可以设置serverSideVerificationOptions属性 30 | GDTServerSideVerificationOptions *ssv = [[GDTServerSideVerificationOptions alloc] init]; 31 | ssv.userIdentifier = self.userId; 32 | ssv.customRewardString = self.customData; 33 | self.rvad.serverSideVerificationOptions = ssv; 34 | [self.rvad loadAd]; 35 | } 36 | 37 | 38 | #pragma mark - GDTRewardVideoAdDelegate 39 | - (void)gdt_rewardVideoAdDidLoad:(GDTRewardVideoAd *)rewardedVideoAd 40 | { 41 | NSLog(@"%s",__FUNCTION__); 42 | UIViewController* controller = [UIApplication sharedApplication].keyWindow.rootViewController; 43 | [self.rvad showAdFromRootViewController:controller]; 44 | // 发送广告事件 45 | [self sendEventAction:onAdLoaded]; 46 | } 47 | 48 | 49 | - (void)gdt_rewardVideoAdVideoDidLoad:(GDTRewardVideoAd *)rewardedVideoAd 50 | { 51 | NSLog(@"%s",__FUNCTION__); 52 | } 53 | 54 | 55 | - (void)gdt_rewardVideoAdWillVisible:(GDTRewardVideoAd *)rewardedVideoAd 56 | { 57 | NSLog(@"%s",__FUNCTION__); 58 | // 发送广告事件 59 | [self sendEventAction:onAdPresent]; 60 | } 61 | 62 | - (void)gdt_rewardVideoAdDidExposed:(GDTRewardVideoAd *)rewardedVideoAd 63 | { 64 | NSLog(@"%s",__FUNCTION__); 65 | NSLog(@"广告已曝光"); 66 | // 发送广告事件 67 | [self sendEventAction:onAdExposure]; 68 | } 69 | 70 | - (void)gdt_rewardVideoAdDidClose:(GDTRewardVideoAd *)rewardedVideoAd 71 | { 72 | NSLog(@"%s",__FUNCTION__); 73 | 74 | NSLog(@"广告已关闭"); 75 | // 发送广告事件 76 | [self sendEventAction:onAdClosed]; 77 | } 78 | 79 | 80 | - (void)gdt_rewardVideoAdDidClicked:(GDTRewardVideoAd *)rewardedVideoAd 81 | { 82 | NSLog(@"%s",__FUNCTION__); 83 | NSLog(@"广告已点击"); 84 | // 发送广告事件 85 | [self sendEventAction:onAdClicked]; 86 | } 87 | 88 | - (void)gdt_rewardVideoAd:(GDTRewardVideoAd *)rewardedVideoAd didFailWithError:(NSError *)error 89 | { 90 | NSLog(@"%s",__FUNCTION__); 91 | NSLog(@"ERROR: %@", error); 92 | // 发送广告错误事件 93 | [self sendErrorEvent:error.code withErrMsg:error.localizedDescription]; 94 | } 95 | 96 | - (void)gdt_rewardVideoAdDidRewardEffective:(GDTRewardVideoAd *)rewardedVideoAd info:(NSDictionary *)info { 97 | NSLog(@"%s",__FUNCTION__); 98 | NSString *transId=[info objectForKey:@"GDT_TRANS_ID"]; 99 | NSLog(@"播放达到激励条件 transid:%@", transId); 100 | // 发送激励事件 101 | FAQAdRewardEvent *event=[[FAQAdRewardEvent alloc] initWithAdId:self.posId transId:transId customData:self.customData userId:self.userId]; 102 | [self sendEvent:event]; 103 | } 104 | 105 | - (void)gdt_rewardVideoAdDidPlayFinish:(GDTRewardVideoAd *)rewardedVideoAd 106 | { 107 | NSLog(@"%s",__FUNCTION__); 108 | NSLog(@"视频播放结束"); 109 | } 110 | @end 111 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQSplashPage.h: -------------------------------------------------------------------------------- 1 | // 2 | // FAQSplashPage.h 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/18. 6 | // 7 | 8 | #import "FAQBaseAdPage.h" 9 | // 开屏广告 10 | @interface FAQSplashPage : FAQBaseAdPage 11 | @end 12 | -------------------------------------------------------------------------------- /ios/Classes/Page/FAQSplashPage.m: -------------------------------------------------------------------------------- 1 | // 2 | // FAQSplashPage.m 3 | // flutter_qq_ads 4 | // 5 | // Created by zero on 2021/8/18. 6 | // 7 | 8 | #import "FAQSplashPage.h" 9 | #import "GDTSplashAd.h" 10 | // 开屏广告 11 | @interface FAQSplashPage() 12 | @property (strong, nonatomic) GDTSplashAd *splashAd; 13 | @property (retain, nonatomic) UIView *bottomView; 14 | @property (nonatomic, assign) BOOL fullScreenAd; 15 | @end 16 | 17 | @implementation FAQSplashPage 18 | // 加载广告 19 | -(void)loadAd:(FlutterMethodCall *)call{ 20 | NSString* logo=call.arguments[@"logo"]; 21 | int fetchDelay=[call.arguments[@"fetchDelay"] intValue]; 22 | // logo 判断为空,则全屏展示 23 | self.fullScreenAd=[logo isKindOfClass:[NSNull class]]||[logo length]==0; 24 | // 初始化开屏广告 25 | self.splashAd=[[GDTSplashAd alloc] initWithPlacementId:self.posId]; 26 | self.splashAd.delegate=self; 27 | // 设置超时时长 28 | self.splashAd.fetchDelay=fetchDelay; 29 | // 加载全屏广告 30 | if(self.fullScreenAd){ 31 | [self.splashAd loadFullScreenAd]; 32 | }else{ 33 | // 加载半屏广告 34 | [self.splashAd loadAd]; 35 | // 设置底部 logo 36 | self.bottomView=nil; 37 | CGSize size=[[UIScreen mainScreen] bounds].size; 38 | CGFloat width=size.width; 39 | CGFloat height=112.5;// 这里按照 15% 进行logo 的展示,防止尺寸不够的问题,750*15%=112.5 40 | self.bottomView=[[UIView alloc]initWithFrame:CGRectMake(0, 0,width, height)]; 41 | self.bottomView.backgroundColor=[UIColor whiteColor]; 42 | UIImageView *logoView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:logo]]; 43 | logoView.frame=CGRectMake(0, 0, width, height); 44 | logoView.contentMode=UIViewContentModeCenter; 45 | logoView.center=self.bottomView.center; 46 | [self.bottomView addSubview:logoView]; 47 | } 48 | } 49 | 50 | 51 | #pragma mark - GDTSplashAdDelegate 52 | 53 | - (void)splashAdDidLoad:(GDTSplashAd *)splashAd { 54 | NSLog(@"splashAdDidLoad"); 55 | UIWindow* mainWin=[[UIApplication sharedApplication] keyWindow]; 56 | // 加载全屏广告 57 | if(self.fullScreenAd){ 58 | [self.splashAd showFullScreenAdInWindow:mainWin withLogoImage:nil skipView:nil]; 59 | }else{ 60 | // 加载半屏广告 61 | [self.splashAd showAdInWindow:mainWin withBottomView:_bottomView skipView:nil]; 62 | } 63 | // 发送广告事件 64 | [self sendEventAction:onAdLoaded]; 65 | } 66 | 67 | - (void)splashAdSuccessPresentScreen:(GDTSplashAd *)splashAd 68 | { 69 | NSLog(@"%s",__FUNCTION__); 70 | // 发送广告事件 71 | [self sendEventAction:onAdPresent]; 72 | } 73 | 74 | - (void)splashAdFailToPresent:(GDTSplashAd *)splashAd withError:(NSError *)error 75 | { 76 | NSLog(@"%s%@",__FUNCTION__,error); 77 | // 发送广告错误事件 78 | [self sendErrorEvent:error.code withErrMsg:error.localizedDescription]; 79 | } 80 | 81 | - (void)splashAdExposured:(GDTSplashAd *)splashAd 82 | { 83 | NSLog(@"%s",__FUNCTION__); 84 | // 发送广告事件 85 | [self sendEventAction:onAdExposure]; 86 | } 87 | 88 | - (void)splashAdClicked:(GDTSplashAd *)splashAd 89 | { 90 | NSLog(@"%s",__FUNCTION__); 91 | // 发送广告事件 92 | [self sendEventAction:onAdClicked]; 93 | } 94 | 95 | - (void)splashAdApplicationWillEnterBackground:(GDTSplashAd *)splashAd 96 | { 97 | NSLog(@"%s",__FUNCTION__); 98 | } 99 | 100 | - (void)splashAdWillClosed:(GDTSplashAd *)splashAd 101 | { 102 | NSLog(@"%s",__FUNCTION__); 103 | } 104 | 105 | - (void)splashAdClosed:(GDTSplashAd *)splashAd 106 | { 107 | NSLog(@"%s",__FUNCTION__); 108 | self.splashAd = nil; 109 | // 发送广告事件 110 | [self sendEventAction:onAdClosed]; 111 | } 112 | 113 | - (void)splashAdWillPresentFullScreenModal:(GDTSplashAd *)splashAd 114 | { 115 | NSLog(@"%s",__FUNCTION__); 116 | } 117 | 118 | - (void)splashAdDidPresentFullScreenModal:(GDTSplashAd *)splashAd 119 | { 120 | NSLog(@"%s",__FUNCTION__); 121 | } 122 | 123 | - (void)splashAdWillDismissFullScreenModal:(GDTSplashAd *)splashAd 124 | { 125 | NSLog(@"%s",__FUNCTION__); 126 | } 127 | 128 | - (void)splashAdDidDismissFullScreenModal:(GDTSplashAd *)splashAd 129 | { 130 | NSLog(@"%s",__FUNCTION__); 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /ios/flutter_qq_ads.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint flutter_qq_ads.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'flutter_qq_ads' 7 | s.version = '2.9.0' 8 | s.summary = '一款优质的 Flutter 广告变现插件(腾讯广告、广点通、优量汇)' 9 | s.description = <<-DESC 10 | 一款优质的 Flutter 广告插件(腾讯广告、广点通、优量汇). 11 | DESC 12 | s.homepage = 'https://flutterads.top/' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'ZeroFlutter' => '1300326388@qq.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.public_header_files = 'Classes/**/*.h' 18 | s.dependency 'Flutter' 19 | # 依赖版本: https://github.com/CocoaPods/Specs/tree/master/Specs/a/a/a/GDTMobSDK 20 | s.dependency 'GDTMobSDK','4.15.00' 21 | s.static_framework = true 22 | # 广点通的 SDK 最低支持 9.0 所以,这里设置 9.0 23 | s.ios.deployment_target = '9.0' 24 | # Flutter.framework does not contain a i386 slice. 25 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 26 | end 27 | -------------------------------------------------------------------------------- /lib/event/ad_error_event.dart: -------------------------------------------------------------------------------- 1 | import 'ad_event.dart'; 2 | 3 | /// 广告错误事件 4 | class AdErrorEvent extends AdEvent { 5 | AdErrorEvent( 6 | {required this.errCode, 7 | required this.errMsg, 8 | required String adId, 9 | required String action}) 10 | : super(adId: adId, action: action); 11 | // 错误码 12 | final int errCode; 13 | // 错误信息 14 | final String errMsg; 15 | // 解析 json 为错误事件对象 16 | factory AdErrorEvent.fromJson(Map json) { 17 | return AdErrorEvent( 18 | errCode: json['errCode'], 19 | errMsg: json['errMsg'], 20 | adId: json['adId'], 21 | action: json['action'], 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/event/ad_event.dart: -------------------------------------------------------------------------------- 1 | import 'ad_error_event.dart'; 2 | import 'ad_event_action.dart'; 3 | import 'ad_reward_event.dart'; 4 | export 'ad_error_event.dart'; 5 | export 'ad_event_action.dart'; 6 | export 'ad_reward_event.dart'; 7 | 8 | /// 广告事件 9 | class AdEvent { 10 | AdEvent({required this.adId, required this.action}); 11 | // 广告 id 12 | final String adId; 13 | // 操作 14 | final String action; 15 | 16 | /// 解析 AdEvent 17 | factory AdEvent.fromJson(Map json) { 18 | String action = json['action']; 19 | if (action == AdEventAction.onAdError) { 20 | return AdErrorEvent.fromJson(json); 21 | } else if (action == AdEventAction.onAdReward) { 22 | return AdRewardEvent.fromJson(json); 23 | } else { 24 | return AdEvent( 25 | adId: json['adId'], 26 | action: action, 27 | ); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/event/ad_event_action.dart: -------------------------------------------------------------------------------- 1 | /// 广告事件操作 2 | class AdEventAction { 3 | // 广告错误 4 | static final String onAdError = "onAdError"; 5 | // 广告加载成功 6 | static final String onAdLoaded = "onAdLoaded"; 7 | // 广告填充 8 | static final String onAdPresent = "onAdPresent"; 9 | // 广告曝光 10 | static final String onAdExposure = "onAdExposure"; 11 | // 广告关闭(开屏计时结束或者用户点击关闭) 12 | static final String onAdClosed = "onAdClosed"; 13 | // 广告点击 14 | static final String onAdClicked = "onAdClicked"; 15 | // 广告跳过 16 | static final String onAdSkip = "onAdSkip"; 17 | // 广告播放或计时完毕 18 | static final String onAdComplete = "onAdComplete"; 19 | // 获得广告激励 20 | static final String onAdReward = "onAdReward"; 21 | } 22 | -------------------------------------------------------------------------------- /lib/event/ad_event_handler.dart: -------------------------------------------------------------------------------- 1 | import 'ad_event.dart'; 2 | export 'ad_event.dart'; 3 | 4 | /// 广告事件回调监听 5 | typedef OnAdEventListener = void Function(AdEvent event); 6 | 7 | /// 处理广告事件 8 | void hanleAdEvent(dynamic data, OnAdEventListener listener) { 9 | if (data != null) { 10 | AdEvent adEvent = AdEvent.fromJson(data); 11 | listener.call(adEvent); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/event/ad_reward_event.dart: -------------------------------------------------------------------------------- 1 | import 'ad_event.dart'; 2 | 3 | /// 广告激励事件 4 | class AdRewardEvent extends AdEvent { 5 | AdRewardEvent( 6 | {required this.transId, 7 | this.customData, 8 | this.userId, 9 | required String adId, 10 | required String action}) 11 | : super(adId: adId, action: action); 12 | // 激励服务端验证的唯一 ID 13 | final String transId; 14 | // 服务端验证的自定义信息 15 | final String? customData; 16 | // 服务端验证的用户信息 17 | final String? userId; 18 | // 解析 json 为激励事件对象 19 | factory AdRewardEvent.fromJson(Map json) { 20 | return AdRewardEvent( 21 | adId: json['adId'], 22 | action: json['action'], 23 | transId: json['transId'], 24 | customData: json['customData'], 25 | userId: json['userId'], 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/flutter_qq_ads.dart: -------------------------------------------------------------------------------- 1 | import 'flutter_qq_ads_platform_interface.dart'; 2 | 3 | import 'event/ad_event_handler.dart'; 4 | export 'event/ad_event_handler.dart'; 5 | export 'view/ad_banner_widget.dart'; 6 | export 'view/ad_feed_widget.dart'; 7 | 8 | /// 腾讯广告 Flutter 插件 9 | class FlutterQqAds { 10 | /// 请求应用跟踪透明度授权 11 | static Future get requestIDFA async { 12 | return FlutterQqAdsPlatform.instance.requestIDFA(); 13 | } 14 | 15 | /// 初始化广告 16 | /// [appId] 应用媒体ID 17 | static Future initAd(String appId) { 18 | return FlutterQqAdsPlatform.instance.initAd(appId); 19 | } 20 | 21 | /// 设置个性化广告 22 | /// 0:代表开启个性化广告推荐,1:代表关闭个性化广告推荐 23 | static Future setPersonalizedState(int state) { 24 | return FlutterQqAdsPlatform.instance.setPersonalizedState(state); 25 | } 26 | 27 | /// 展示开屏广告 28 | /// [posId] 广告位 id 29 | /// [logo] 如果传值则展示底部logo,不传不展示,则全屏展示 30 | /// [fetchDelay] 拉取广告的超时时间,默认值 3 秒,取值范围[1.5~5]秒 31 | static Future showSplashAd(String posId, 32 | {String? logo, double fetchDelay = 3}) { 33 | return FlutterQqAdsPlatform.instance.showSplashAd( 34 | posId, 35 | logo: logo, 36 | fetchDelay: fetchDelay, 37 | ); 38 | } 39 | 40 | /// 展示插屏广告 41 | /// [posId] 广告位 id 42 | /// [showPopup] Popup 形式显示(仅 Android) 43 | /// [showFullScreenVideo] 插屏全屏视频形式显示 44 | /// [showRewardVideo] 插屏激励视频形式显示 45 | /// [autoPlayOnWifi] 是否仅在 WiFi 网络下自动播放 46 | /// [autoPlayMuted] 自动播放是否静音 47 | /// [customData] 设置服务端验证的自定义信息 48 | /// [userId] 设置服务端验证的用户信息 49 | static Future showInterstitialAd( 50 | String posId, { 51 | bool showPopup = false, 52 | bool showFullScreenVideo = false, 53 | bool showRewardVideo = false, 54 | bool autoPlayOnWifi = false, 55 | bool autoPlayMuted = true, 56 | String? customData, 57 | String? userId, 58 | }) { 59 | return FlutterQqAdsPlatform.instance.showInterstitialAd( 60 | posId, 61 | showPopup: showPopup, 62 | showFullScreenVideo: showFullScreenVideo, 63 | showRewardVideo: showRewardVideo, 64 | autoPlayOnWifi: autoPlayOnWifi, 65 | autoPlayMuted: autoPlayMuted, 66 | customData: customData, 67 | userId: userId, 68 | ); 69 | } 70 | 71 | /// 展示激励视频广告 72 | /// [posId] 广告位 id 73 | /// [playMuted] 是否静音播放 74 | /// [customData] 设置服务端验证的自定义信息 75 | /// [userId] 设置服务端验证的用户信息 76 | static Future showRewardVideoAd( 77 | String posId, { 78 | bool playMuted = false, 79 | String? customData, 80 | String? userId, 81 | }) { 82 | return FlutterQqAdsPlatform.instance.showRewardVideoAd( 83 | posId, 84 | playMuted: playMuted, 85 | customData: customData, 86 | userId: userId, 87 | ); 88 | } 89 | 90 | /// 加载信息流广告列表 91 | /// [posId] 广告位 id 92 | /// [width] 宽度 93 | /// [height] 高度,0:代表自适应广告高度 94 | /// [count] 获取广告数量,建议 1~3 个 95 | static Future> loadFeedAd(String posId, 96 | {int width = 375, int height = 0, int count = 1}) { 97 | return FlutterQqAdsPlatform.instance.loadFeedAd( 98 | posId, 99 | width: width, 100 | height: height, 101 | count: count, 102 | ); 103 | } 104 | 105 | /// 清除信息流广告列表 106 | /// [list] 信息流广告 id 列表 107 | static Future clearFeedAd(List list) { 108 | return FlutterQqAdsPlatform.instance.clearFeedAd(list); 109 | } 110 | 111 | static Future get platformVersion async { 112 | return FlutterQqAdsPlatform.instance.getPlatformVersion(); 113 | } 114 | 115 | ///事件回调 116 | ///@params onData 事件回调 117 | static Future onEventListener(OnAdEventListener onAdEventListener) { 118 | return FlutterQqAdsPlatform.instance.onEventListener(onAdEventListener); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/flutter_qq_ads_method_channel.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/services.dart'; 5 | 6 | import 'event/ad_event_handler.dart'; 7 | import 'flutter_qq_ads_platform_interface.dart'; 8 | 9 | class MethodChannelFlutterQqAds extends FlutterQqAdsPlatform { 10 | @visibleForTesting 11 | final methodChannel = const MethodChannel('flutter_qq_ads'); 12 | 13 | @visibleForTesting 14 | final eventChannel = const EventChannel('flutter_qq_ads_event'); 15 | 16 | @override 17 | Future requestIDFA() async { 18 | if (Platform.isIOS) { 19 | final bool result = await methodChannel.invokeMethod('requestIDFA'); 20 | return result; 21 | } 22 | return true; 23 | } 24 | 25 | @override 26 | Future initAd(String appId) async { 27 | final bool result = await methodChannel.invokeMethod( 28 | 'initAd', 29 | {'appId': appId}, 30 | ); 31 | print( 32 | "🎉🎉🎉 FlutterAds ==> 初始化完成,推荐使用 GroMore Pro 版本,获得更高的收益:https://flutterads.top/"); 33 | return result; 34 | } 35 | 36 | @override 37 | Future setPersonalizedState(int state) async { 38 | final bool result = await methodChannel.invokeMethod( 39 | 'setPersonalizedState', 40 | {'state': state}, 41 | ); 42 | return result; 43 | } 44 | 45 | @override 46 | Future showSplashAd(String posId, 47 | {String? logo, double fetchDelay = 3}) async { 48 | final bool result = await methodChannel.invokeMethod( 49 | 'showSplashAd', 50 | { 51 | 'posId': posId, 52 | 'logo': logo, 53 | 'fetchDelay': fetchDelay, 54 | }, 55 | ); 56 | return result; 57 | } 58 | 59 | @override 60 | Future showInterstitialAd( 61 | String posId, { 62 | bool showPopup = false, 63 | bool showFullScreenVideo = false, 64 | bool showRewardVideo = false, 65 | bool autoPlayOnWifi = false, 66 | bool autoPlayMuted = true, 67 | String? customData, 68 | String? userId, 69 | }) async { 70 | final bool result = await methodChannel.invokeMethod( 71 | 'showInterstitialAd', 72 | { 73 | 'posId': posId, 74 | 'showPopup': showPopup, 75 | 'showFullScreenVideo': showFullScreenVideo, 76 | 'showRewardVideo': showRewardVideo, 77 | 'autoPlayOnWifi': autoPlayOnWifi, 78 | 'autoPlayMuted': autoPlayMuted, 79 | 'customData': customData, 80 | 'userId': userId, 81 | }, 82 | ); 83 | return result; 84 | } 85 | 86 | @override 87 | Future showRewardVideoAd( 88 | String posId, { 89 | bool playMuted = false, 90 | String? customData, 91 | String? userId, 92 | }) async { 93 | final bool result = await methodChannel.invokeMethod( 94 | 'showRewardVideoAd', 95 | { 96 | 'posId': posId, 97 | 'playMuted': playMuted, 98 | 'customData': customData, 99 | 'userId': userId, 100 | }, 101 | ); 102 | return result; 103 | } 104 | 105 | @override 106 | Future> loadFeedAd(String posId, 107 | {int width = 375, int height = 0, int count = 1}) async { 108 | final List result = await methodChannel.invokeMethod( 109 | 'loadFeedAd', 110 | { 111 | 'posId': posId, 112 | 'width': width, 113 | 'height': height, 114 | 'count': count, 115 | }, 116 | ); 117 | return List.from(result); 118 | } 119 | 120 | @override 121 | Future clearFeedAd(List list) async { 122 | final bool result = await methodChannel.invokeMethod( 123 | 'clearFeedAd', 124 | { 125 | 'list': list, 126 | }, 127 | ); 128 | return result; 129 | } 130 | 131 | @override 132 | Future getPlatformVersion() async { 133 | final String version = 134 | await methodChannel.invokeMethod('getPlatformVersion'); 135 | return version; 136 | } 137 | 138 | @override 139 | Future onEventListener(OnAdEventListener onAdEventListener) async { 140 | eventChannel.receiveBroadcastStream().listen((data) { 141 | hanleAdEvent(data, onAdEventListener); 142 | }); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/flutter_qq_ads_platform_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:plugin_platform_interface/plugin_platform_interface.dart'; 2 | 3 | import 'flutter_qq_ads_method_channel.dart'; 4 | import 'event/ad_event_handler.dart'; 5 | 6 | abstract class FlutterQqAdsPlatform extends PlatformInterface { 7 | FlutterQqAdsPlatform() : super(token: _token); 8 | 9 | static final Object _token = Object(); 10 | 11 | static FlutterQqAdsPlatform _instance = MethodChannelFlutterQqAds(); 12 | 13 | static FlutterQqAdsPlatform get instance => _instance; 14 | 15 | static set instance(FlutterQqAdsPlatform instance) { 16 | PlatformInterface.verifyToken(instance, _token); 17 | _instance = instance; 18 | } 19 | 20 | Future requestIDFA() { 21 | throw UnimplementedError('requestIDFA() has not been implemented.'); 22 | } 23 | 24 | Future initAd(String appId) { 25 | throw UnimplementedError('initAd() has not been implemented.'); 26 | } 27 | 28 | Future setPersonalizedState(int state) { 29 | throw UnimplementedError( 30 | 'setPersonalizedState() has not been implemented.'); 31 | } 32 | 33 | Future showSplashAd(String posId, 34 | {String? logo, double fetchDelay = 3}) { 35 | throw UnimplementedError('showSplashAd() has not been implemented.'); 36 | } 37 | 38 | Future showInterstitialAd( 39 | String posId, { 40 | bool showPopup = false, 41 | bool showFullScreenVideo = false, 42 | bool showRewardVideo = false, 43 | bool autoPlayOnWifi = false, 44 | bool autoPlayMuted = true, 45 | String? customData, 46 | String? userId, 47 | }) { 48 | throw UnimplementedError('showInterstitialAd() has not been implemented.'); 49 | } 50 | 51 | Future showRewardVideoAd( 52 | String posId, { 53 | bool playMuted = false, 54 | String? customData, 55 | String? userId, 56 | }) { 57 | throw UnimplementedError('showRewardVideoAd() has not been implemented.'); 58 | } 59 | 60 | Future> loadFeedAd(String posId, 61 | {int width = 375, int height = 0, int count = 1}) { 62 | throw UnimplementedError('loadFeedAd() has not been implemented.'); 63 | } 64 | 65 | Future clearFeedAd(List list) { 66 | throw UnimplementedError('clearFeedAd() has not been implemented.'); 67 | } 68 | 69 | Future getPlatformVersion() { 70 | throw UnimplementedError('platformVersion() has not been implemented.'); 71 | } 72 | 73 | Future onEventListener(OnAdEventListener onAdEventListener) { 74 | throw UnimplementedError('onEventListener() has not been implemented.'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/view/ad_banner_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter/services.dart'; 5 | 6 | /// Banner 广告组件 7 | class AdBannerWidget extends StatefulWidget { 8 | AdBannerWidget({ 9 | Key? key, 10 | required this.posId, 11 | this.interval = 30, 12 | this.show = true, 13 | this.width = 300, 14 | this.height = 150, 15 | }) : super(key: key); 16 | // 广告 id 17 | final String posId; 18 | // 广告刷新间隔,0 或[30~120]之间的数字,单位为 s,默认 30s 19 | // Android:0 表示不自动轮播 20 | // iOS:0 表示关闭轮播动画,因为 iOS 没有不轮播 21 | final int interval; 22 | // 宽高 23 | final double width, height; 24 | // 是否显示广告 25 | final bool show; 26 | 27 | @override 28 | _AdBannerWidgetState createState() => _AdBannerWidgetState(); 29 | } 30 | 31 | class _AdBannerWidgetState extends State { 32 | // View 类型 33 | final String viewType = 'flutter_qq_ads_banner'; 34 | // 创建参数 35 | late Map creationParams; 36 | 37 | @override 38 | void initState() { 39 | creationParams = { 40 | "posId": widget.posId, 41 | "interval": widget.interval 42 | }; 43 | super.initState(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | if (!widget.show) { 49 | return SizedBox.shrink(); 50 | } 51 | Widget view; 52 | if (Platform.isIOS) { 53 | view = UiKitView( 54 | viewType: viewType, 55 | creationParams: creationParams, 56 | creationParamsCodec: const StandardMessageCodec(), 57 | ); 58 | } else { 59 | view = AndroidView( 60 | viewType: viewType, 61 | creationParams: creationParams, 62 | creationParamsCodec: const StandardMessageCodec(), 63 | ); 64 | } 65 | return SizedBox.fromSize( 66 | size: Size(widget.width, widget.height), 67 | child: view, 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/view/ad_feed_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | 6 | /// Feed 信息流广告组件 7 | /// 建议在个性化模板的广告view中,宽度自动铺满整个view,期望模板尺寸的参数设置中,高度可以设置为0,高度会自适应,达到最佳的展示比例 8 | class AdFeedWidget extends StatefulWidget { 9 | AdFeedWidget({ 10 | Key? key, 11 | required this.posId, 12 | this.show = true, 13 | this.width = 375, 14 | this.height = 128, 15 | }) : super(key: key); 16 | // 返回的广告 id,这里不是广告位id 17 | final String posId; 18 | // 是否显示广告 19 | final bool show; 20 | // 这里的宽高只是展示 widget 宽高,最终会自动适配实际广告的宽高,不可设置为 0 21 | final double width, height; 22 | 23 | @override 24 | _AdFeedWidgetState createState() => _AdFeedWidgetState(); 25 | } 26 | 27 | class _AdFeedWidgetState extends State 28 | with AutomaticKeepAliveClientMixin { 29 | // View 类型 30 | final String viewType = 'flutter_qq_ads_feed'; 31 | // 创建参数 32 | late Map creationParams; 33 | 34 | @override 35 | void initState() { 36 | creationParams = { 37 | "posId": widget.posId, 38 | }; 39 | super.initState(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | super.build(context); 45 | if (!widget.show || widget.width <= 0 || widget.height <= 0) { 46 | return SizedBox.shrink(); 47 | } 48 | Widget view; 49 | if (Platform.isIOS) { 50 | view = UiKitView( 51 | viewType: viewType, 52 | creationParams: creationParams, 53 | creationParamsCodec: const StandardMessageCodec(), 54 | ); 55 | } else { 56 | view = AndroidView( 57 | viewType: viewType, 58 | creationParams: creationParams, 59 | creationParamsCodec: const StandardMessageCodec(), 60 | ); 61 | } 62 | return SizedBox.fromSize( 63 | size: Size(widget.width, widget.height), 64 | child: view, 65 | ); 66 | } 67 | 68 | @override 69 | bool get wantKeepAlive => true; 70 | } 71 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_qq_ads 2 | description: 【持续更新】一款优质的腾讯优量汇 Flutter 广告变现插件,支持 Android、iOS 平台,可快速接入进行广告变现。 3 | version: 2.9.0 4 | homepage: https://flutterads.top 5 | repository: https://github.com/FlutterAds/flutter_qq_ads 6 | issue_tracker: https://github.com/FlutterAds/flutter_qq_ads/issues 7 | documentation: https://github.com/FlutterAds/flutter_qq_ads/wiki 8 | topics: [flutterads, ads, qq, adnet, income] 9 | funding: [https://github.com/FlutterAds/.github] 10 | 11 | environment: 12 | sdk: ">=2.12.0 <4.0.0" 13 | flutter: ">=2.5.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | plugin_platform_interface: ^2.1.0 19 | 20 | dev_dependencies: 21 | flutter_test: 22 | sdk: flutter 23 | # The following section is specific to Flutter. 24 | flutter: 25 | plugin: 26 | platforms: 27 | android: 28 | package: com.zero.flutter_qq_ads 29 | pluginClass: FlutterQqAdsPlugin 30 | ios: 31 | pluginClass: FlutterQqAdsPlugin 32 | -------------------------------------------------------------------------------- /test/ads_config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | /// 广告配置信息 4 | class AdsConfig { 5 | /// 获取 Logo 资源名称 6 | static String get logo { 7 | if (Platform.isAndroid) { 8 | return 'flutterads_logo'; 9 | } else { 10 | return 'LaunchImage'; 11 | } 12 | } 13 | 14 | /// 获取 Logo 资源名称 2 15 | static String get logo2 { 16 | if (Platform.isAndroid) { 17 | return 'flutterads_logo2'; 18 | } else { 19 | return 'LaunchImage2'; 20 | } 21 | } 22 | 23 | /// 获取 App id 24 | static String get appId { 25 | // 官方 demo id 26 | // com.qq.gdt.GDTMobSample 27 | // return '1105344611'; 28 | // com.banjixiaoguanjia.app 29 | if (Platform.isAndroid) { 30 | return '1200012024'; 31 | } else { 32 | return '1200018693'; 33 | } 34 | } 35 | 36 | /// 获取开屏广告位id 37 | static String get splashId { 38 | // 官方demo 39 | // return "9040714184494018"; 40 | if (Platform.isAndroid) { 41 | return '8022311121246224'; 42 | } else { 43 | return '5052818319908354'; 44 | } 45 | } 46 | 47 | /// 获取插屏广告位id 48 | static String get interstitialId { 49 | // 官方demo 50 | // return "9040714184494018"; 51 | if (Platform.isAndroid) { 52 | return '3022327103988804'; 53 | } else { 54 | return '5092321153081845'; 55 | } 56 | } 57 | 58 | /// 获取插屏全屏视频广告位id 59 | static String get interstitialFullScreenVideoId { 60 | // 官方demo 61 | // return "9040714184494018"; 62 | if (Platform.isAndroid) { 63 | return '3012521499614895'; 64 | } else { 65 | return '3092322459911886'; 66 | } 67 | } 68 | 69 | /// 获取插屏激励视频广告位id 70 | static String get interstitialRewardVideoId { 71 | // 官方demo 72 | // return "9040714184494018"; 73 | if (Platform.isAndroid) { 74 | return '2052820580637311'; 75 | } else { 76 | return '9022927550132316'; 77 | } 78 | } 79 | 80 | /// 获取激励视频广告位id 81 | static String get rewardVideoId { 82 | // 官方demo 83 | // return "9040714184494018"; 84 | if (Platform.isAndroid) { 85 | return '3032129193181886'; 86 | } else { 87 | return '1042528123383807'; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/flutter_qq_ads_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_qq_ads/flutter_qq_ads.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | import 'ads_config.dart'; 6 | 7 | void main() { 8 | const MethodChannel channel = MethodChannel('flutter_qq_ads'); 9 | 10 | TestWidgetsFlutterBinding.ensureInitialized(); 11 | 12 | setUp(() { 13 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 14 | .setMockMethodCallHandler(channel, (MethodCall call) async { 15 | String method = call.method; 16 | if (method == 'initAd') { 17 | String appId = call.arguments['appId'] ?? ''; 18 | return appId.isNotEmpty; 19 | } else if (method == 'setPersonalizedState') { 20 | int state = call.arguments['state'] ?? 0; 21 | return state == 0 || state == 1; 22 | } else if (method == 'showSplashAd') { 23 | String posId = call.arguments['posId'] ?? ''; 24 | return posId.isNotEmpty; 25 | } else if (method == 'showInterstitialAd') { 26 | String posId = call.arguments['posId'] ?? ''; 27 | return posId.isNotEmpty; 28 | } else if (method == 'showRewardVideoAd') { 29 | String posId = call.arguments['posId'] ?? ''; 30 | return posId.isNotEmpty; 31 | } 32 | return false; 33 | }); 34 | }); 35 | 36 | tearDown(() { 37 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 38 | .setMockMethodCallHandler(channel, null); 39 | }); 40 | 41 | test('initAd', () async { 42 | expect(await FlutterQqAds.initAd(AdsConfig.appId), true); 43 | }); 44 | 45 | test('setPersonalizedState', () async { 46 | expect(await FlutterQqAds.setPersonalizedState(1), true); 47 | }); 48 | test('showSplashAd', () async { 49 | expect(await FlutterQqAds.showSplashAd(AdsConfig.splashId), true); 50 | }); 51 | test('showInterstitialAd', () async { 52 | expect( 53 | await FlutterQqAds.showInterstitialAd(AdsConfig.interstitialId), true); 54 | expect( 55 | await FlutterQqAds.showInterstitialAd( 56 | AdsConfig.interstitialFullScreenVideoId), 57 | true); 58 | expect( 59 | await FlutterQqAds.showInterstitialAd( 60 | AdsConfig.interstitialRewardVideoId), 61 | true); 62 | }); 63 | test('showRewardVideoAd', () async { 64 | expect(await FlutterQqAds.showRewardVideoAd(AdsConfig.rewardVideoId), true); 65 | }); 66 | } 67 | --------------------------------------------------------------------------------