Skip to content

Commit fdb8312

Browse files
xtelincomravn-google
authored andcommitted
Banner Ad Positioning (flutter#389)
1 parent 92aca45 commit fdb8312

File tree

8 files changed

+82
-14
lines changed

8 files changed

+82
-14
lines changed

packages/firebase_admob/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ Ads must be loaded before they're shown.
4545
```
4646
myBanner
4747
..load() // typically this happens well before the ad is shown
48-
..show();
48+
..show(anchorOffset: 60.0, anchorType: AnchorType.anchorBottom);
49+
// Positions the banner ad 60 pixels from the bottom of the screen
4950
// InterstitialAds are loaded and shown in the same way
5051
```
5152

@@ -99,8 +100,8 @@ method.
99100
This is just an initial version of the plugin. There are still some
100101
limitiations:
101102

102-
- Banner ads always appear at the bottom of the screen. They can't be positioned
103-
or animated into view.
103+
- Banner ads have limited positioning functionality. They can be positioned at the top or the bottom of the screen and at a logical pixel offset from the edge.
104+
- Banner ads cannot be animated into view.
104105
- It's not possible to specify a banner ad's size.
105106
- There's no support for native ads.
106107
- The existing tests are fairly rudimentary.

packages/firebase_admob/android/src/main/java/io/flutter/plugins/firebaseadmob/FirebaseAdMobPlugin.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package io.flutter.plugins.firebaseadmob;
66

77
import android.app.Activity;
8+
import android.view.Gravity;
89
import com.google.android.gms.ads.MobileAds;
910
import com.google.firebase.FirebaseApp;
1011
import io.flutter.plugin.common.MethodCall;
@@ -93,6 +94,13 @@ private void callShowAd(int id, MethodCall call, Result result) {
9394
result.error("ad_not_loaded", "show failed, the specified ad was not loaded id=" + id, null);
9495
return;
9596
}
97+
if (call.argument("anchorOffset") != null) {
98+
ad.anchorOffset = Double.parseDouble((String) call.argument("anchorOffset"));
99+
}
100+
if (call.argument("anchorType") != null) {
101+
ad.anchorType = call.argument("anchorType").equals("bottom") ? Gravity.BOTTOM : Gravity.TOP;
102+
}
103+
96104
ad.show();
97105
result.success(Boolean.TRUE);
98106
}

packages/firebase_admob/android/src/main/java/io/flutter/plugins/firebaseadmob/MobileAd.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ abstract class MobileAd extends AdListener {
2727
final MethodChannel channel;
2828
final int id;
2929
Status status;
30+
double anchorOffset;
31+
int anchorType;
3032

3133
enum Status {
3234
CREATED,
@@ -41,6 +43,8 @@ private MobileAd(int id, Activity activity, MethodChannel channel) {
4143
this.activity = activity;
4244
this.channel = channel;
4345
this.status = Status.CREATED;
46+
this.anchorOffset = 0.0;
47+
this.anchorType = Gravity.BOTTOM;
4448
allAds.put(id, this);
4549
}
4650

@@ -150,8 +154,15 @@ void show() {
150154
LinearLayout content = new LinearLayout(activity);
151155
content.setId(id);
152156
content.setOrientation(LinearLayout.VERTICAL);
153-
content.setGravity(Gravity.BOTTOM);
157+
content.setGravity(anchorType);
154158
content.addView(adView);
159+
final float scale = activity.getResources().getDisplayMetrics().density;
160+
161+
if (anchorType == Gravity.BOTTOM) {
162+
content.setPadding(0, 0, 0, (int) (anchorOffset * scale));
163+
} else {
164+
content.setPadding(0, (int) (anchorOffset * scale), 0, 0);
165+
}
155166

156167
activity.addContentView(
157168
content,

packages/firebase_admob/ios/Classes/FLTMobileAd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ typedef enum : NSUInteger {
1919
- (FLTMobileAdStatus)status;
2020
- (void)loadWithAdUnitId:(NSString *)adUnitId targetingInfo:(NSDictionary *)targetingInfo;
2121
- (void)show;
22+
- (void)showAtOffset:(double)anchorOffset fromAnchor:(int)anchorType;
2223
- (void)dispose;
2324
@end
2425

packages/firebase_admob/ios/Classes/FLTMobileAd.m

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@ @implementation FLTMobileAd
1313
NSNumber *_mobileAdId;
1414
FlutterMethodChannel *_channel;
1515
FLTMobileAdStatus _status;
16+
double _anchorOffset;
17+
int _anchorType;
1618

1719
+ (void)initialize {
1820
if (allAds == nil) {
1921
allAds = [[NSMutableDictionary alloc] init];
2022
}
23+
_anchorType = 0;
24+
_anchorOffset = 0;
2125

2226
if (statusToString == nil) {
2327
statusToString = @{
@@ -48,6 +52,8 @@ - (instancetype)initWithId:(NSNumber *)mobileAdId channel:(FlutterMethodChannel
4852
_mobileAdId = mobileAdId;
4953
_channel = channel;
5054
_status = CREATED;
55+
_anchorOffset = 0;
56+
_anchorType = 0;
5157
allAds[mobileAdId] = self;
5258
}
5359
return self;
@@ -61,6 +67,15 @@ - (void)loadWithAdUnitId:(NSString *)adUnitId targetingInfo:(NSDictionary *)targ
6167
// Implemented by the Banner and Interstitial subclasses
6268
}
6369

70+
- (void)showAtOffset:(double)anchorOffset fromAnchor:(int)anchorType {
71+
_anchorType = anchorType;
72+
_anchorOffset = anchorOffset;
73+
if (_anchorType == 0) {
74+
_anchorOffset = -_anchorOffset;
75+
}
76+
[self show];
77+
}
78+
6479
- (void)show {
6580
// Implemented by the Banner and Interstitial subclasses
6681
}
@@ -105,6 +120,7 @@ - (void)show {
105120
_status = PENDING;
106121
return;
107122
}
123+
108124
if (_status != LOADED) return;
109125

110126
_banner.translatesAutoresizingMaskIntoConstraints = NO;
@@ -116,7 +132,9 @@ - (void)show {
116132
UILayoutGuide *guide = screen.safeAreaLayoutGuide;
117133
[NSLayoutConstraint activateConstraints:@[
118134
[_banner.centerXAnchor constraintEqualToAnchor:guide.centerXAnchor],
119-
[_banner.bottomAnchor constraintEqualToAnchor:guide.bottomAnchor]
135+
[_banner.bottomAnchor
136+
constraintEqualToAnchor:_anchorType == 0 ? guide.bottomAnchor : guide.topAnchor
137+
constant:_anchorOffset]
120138
]];
121139
} else {
122140
[self placeBannerPreIos11];
@@ -129,7 +147,12 @@ - (void)show {
129147
- (void)placeBannerPreIos11 {
130148
UIView *screen = [FLTMobileAd rootViewController].view;
131149
CGFloat x = screen.frame.size.width / 2 - _banner.frame.size.width / 2;
132-
CGFloat y = screen.frame.size.height - _banner.frame.size.height;
150+
CGFloat y;
151+
if (_anchorType == 0) {
152+
y = screen.frame.size.height - _banner.frame.size.height + _anchorOffset;
153+
} else {
154+
y = _anchorOffset;
155+
}
133156
_banner.frame = (CGRect){{x, y}, _banner.frame.size};
134157
[screen addSubview:_banner];
135158
}

packages/firebase_admob/ios/Classes/FirebaseAdMobPlugin.m

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,16 @@ - (void)callShowAd:(NSNumber *)mobileAdId
122122
result([FlutterError errorWithCode:@"ad_not_loaded" message:message details:nil]);
123123
}
124124

125-
[ad show];
125+
double offset = 0.0;
126+
int type = 0;
127+
if (call.arguments[@"anchorOffset"] != nil) {
128+
offset = [call.arguments[@"anchorOffset"] doubleValue];
129+
}
130+
if (call.arguments[@"anchorType"] != nil) {
131+
type = [call.arguments[@"anchorType"] isEqualToString:@"bottom"] ? 0 : 1;
132+
}
133+
134+
[ad showAtOffset:offset fromAnchor:type];
126135
result([NSNumber numberWithBool:YES]);
127136
}
128137

packages/firebase_admob/lib/firebase_admob.dart

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ class MobileAdTargetingInfo {
8282
}
8383
}
8484

85+
enum AnchorType { bottom, top }
86+
8587
/// A mobile [BannerAd] or [InterstitialAd] for the [FirebaseAdMobPlugin].
8688
///
8789
/// A [MobileAd] must be loaded with [load] before it is shown with [show].
@@ -128,8 +130,16 @@ abstract class MobileAd {
128130
///
129131
/// The [listener] will be notified when the ad has finished loading or fails
130132
/// to do so. An ad that fails to load will not be shown.
131-
Future<bool> show() {
132-
return _invokeBooleanMethod("showAd", <String, dynamic>{'id': id});
133+
///
134+
/// anchorOffset is the logical pixel offset from the edge of the screen (default 0.0)
135+
/// anchorType place advert at top or bottom of screen (default bottom)
136+
Future<bool> show(
137+
{double anchorOffset = 0.0, AnchorType anchorType = AnchorType.bottom}) {
138+
return _invokeBooleanMethod("showAd", <String, dynamic>{
139+
'id': id,
140+
'anchorOffset': anchorOffset.toString(),
141+
'anchorType': anchorType == AnchorType.top ? "top" : "bottom"
142+
});
133143
}
134144

135145
/// Free the plugin resources associated with this ad.
@@ -166,8 +176,7 @@ class BannerAd extends MobileAd {
166176
@required String adUnitId,
167177
MobileAdTargetingInfo targetingInfo,
168178
MobileAdListener listener,
169-
})
170-
: super(
179+
}) : super(
171180
adUnitId: adUnitId,
172181
targetingInfo: targetingInfo,
173182
listener: listener);
@@ -192,8 +201,7 @@ class InterstitialAd extends MobileAd {
192201
String adUnitId,
193202
MobileAdTargetingInfo targetingInfo,
194203
MobileAdListener listener,
195-
})
196-
: super(
204+
}) : super(
197205
adUnitId: adUnitId,
198206
targetingInfo: targetingInfo,
199207
listener: listener);

packages/firebase_admob/test/firebase_admob_test.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ void main() {
6767
}),
6868
isMethodCall('showAd', arguments: <String, dynamic>{
6969
'id': id,
70+
'anchorOffset': '0.0',
71+
'anchorType': 'bottom',
7072
}),
7173
isMethodCall('disposeAd', arguments: <String, dynamic>{
7274
'id': id,
@@ -83,7 +85,10 @@ void main() {
8385
final int id = interstitial.id;
8486

8587
expect(await interstitial.load(), true);
86-
expect(await interstitial.show(), true);
88+
expect(
89+
await interstitial.show(
90+
anchorOffset: 60.0, anchorType: AnchorType.top),
91+
true);
8792
expect(await interstitial.dispose(), true);
8893

8994
expect(log, <Matcher>[
@@ -94,6 +99,8 @@ void main() {
9499
}),
95100
isMethodCall('showAd', arguments: <String, dynamic>{
96101
'id': id,
102+
'anchorOffset': '60.0',
103+
'anchorType': 'top',
97104
}),
98105
isMethodCall('disposeAd', arguments: <String, dynamic>{
99106
'id': id,

0 commit comments

Comments
 (0)