基于Google Flutter官方Codelab,如何在Flutter ListView中借助google_mobile_ads实现多组不同原生广告?
Hey there! I’ve tackled this exact scenario before with the google_mobile_ads plugin, so let me walk you through how to pull off showing different native ads at specific positions in your ListView.
Core Idea
The key is to merge your regular content items and ad entries into a single data list, then render each item based on its type. This lets you insert distinct native ads (with different ad unit IDs or layouts) wherever you need them.
Step 1: Define a Unified List Item Model
First, create a model to distinguish between regular content and different ad types. This helps the ListView know what to render for each entry:
enum ListItemType { content, nativeAdPromo, nativeAdSponsored } class ListItem { final ListItemType type; final dynamic data; // Holds content text or ad unit ID ListItem(this.type, this.data); }
Step 2: Build Your Combined Data Source
Generate a list that mixes your regular content with the ad positions you want. For example, insert a "promo" ad at index 2 and a "sponsored" ad at index 7:
List<ListItem> buildMixedList() { final items = <ListItem>[]; // Add 10 regular content items for (int i = 0; i < 10; i++) { items.add(ListItem(ListItemType.content, "Regular Content Item $i")); } // Insert different ads at specific positions items.insert(2, ListItem(ListItemType.nativeAdPromo, "ca-app-pub-XXX/XXX")); items.insert(7, ListItem(ListItemType.nativeAdSponsored, "ca-app-pub-YYY/YYY")); return items; }
Step 3: Handle Ad Loading & Caching
Create a way to load and cache native ads by their ad unit ID. This prevents redundant loads and keeps track of which ads are ready:
final Map<String, NativeAd?> _loadedAds = {}; void _loadAd(String adUnitId) { if (_loadedAds.containsKey(adUnitId)) return; final nativeAd = NativeAd( adUnitId: adUnitId, factoryId: adUnitId.contains("XXX") ? "promo_ad_factory" : "sponsored_ad_factory", request: const AdRequest(), listener: NativeAdListener( onAdLoaded: (ad) => setState(() => _loadedAds[adUnitId] = ad as NativeAd), onAdFailedToLoad: (ad, error) { ad.dispose(); _loadedAds.remove(adUnitId); print("Ad failed to load: $error"); }, ), ); nativeAd.load(); }
Note: Use different factoryId values if you want distinct layouts for each ad type (you'll need to configure these in your Android/iOS project).
Step 4: Render the ListView
In your build method, map each list item to the correct widget. Show loading states while ads are loading, and the ad once it's ready:
@override Widget build(BuildContext context) { final mixedList = buildMixedList(); // Preload ads when the widget initializes for (final item in mixedList) { if (item.type == ListItemType.nativeAdPromo || item.type == ListItemType.nativeAdSponsored) { _loadAd(item.data as String); } } return ListView.builder( itemCount: mixedList.length, itemBuilder: (context, index) { final item = mixedList[index]; switch (item.type) { case ListItemType.content: return ListTile(title: Text(item.data as String)); case ListItemType.nativeAdPromo: case ListItemType.nativeAdSponsored: final ad = _loadedAds[item.data as String]; if (ad == null) { return const SizedBox( height: 120, child: Center(child: CircularProgressIndicator()), ); } return Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), child: NativeAdWidget(ad: ad), ); } }, ); }
Step 5: Clean Up Resources
Don't forget to dispose of loaded ads when the widget is destroyed to avoid memory leaks:
@override void dispose() { for (final ad in _loadedAds.values) { ad?.dispose(); } super.dispose(); }
Pro Tips
- Lazy Loading: Instead of preloading all ads upfront, use a
ScrollControllerorVisibilityDetectorto load ads only when they're about to enter the viewport. This saves bandwidth and improves performance. - Error Handling: Replace loading indicators with a placeholder widget or skip the ad position entirely if an ad fails to load.
- Custom Layouts: Use different
factoryIdvalues to style each ad type uniquely (configure these in your native project files).
内容的提问来源于stack exchange,提问作者Calvin




