Skip to content

Commit 6a5f10b

Browse files
e-adrienamantoux
authored andcommitted
[webview_flutter] Add a backgroundColor option to the Android webview (flutter#4569)
This PR add an option to set the background color of the Android webview. Part of: flutter#3431 Part of: flutter/flutter#29300
1 parent 73cf5e6 commit 6a5f10b

File tree

17 files changed

+275
-14
lines changed

17 files changed

+275
-14
lines changed

packages/webview_flutter/webview_flutter_android/CHANGELOG.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.5.0
2+
3+
* Adds an option to set the background color of the webview.
4+
15
## 2.4.0
26

37
* Adds support for Android's `WebView.loadData` and `WebView.loadDataWithBaseUrl` methods and implements the `loadFile` and `loadHtmlString` methods from the platform interface.
@@ -27,13 +31,12 @@ when it is created without Hybrid Composition.
2731

2832
## 2.0.15
2933

30-
* Added Overrides in FlutterWebView.java
31-
34+
* Added Overrides in FlutterWebView.java
35+
3236
## 2.0.14
3337

34-
* Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package).
38+
* Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package).
3539

3640
## 2.0.13
3741

3842
* Extract Android implementation from `webview_flutter`.
39-

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ void loadDataWithBaseUrl(
223223

224224
void setWebChromeClient(Long instanceId, Long clientInstanceId);
225225

226+
void setBackgroundColor(Long instanceId, Long color);
227+
226228
/** The codec used by WebViewHostApi. */
227229
static MessageCodec<Object> getCodec() {
228230
return WebViewHostApiCodec.INSTANCE;
@@ -958,6 +960,37 @@ public void error(Throwable error) {
958960
channel.setMessageHandler(null);
959961
}
960962
}
963+
{
964+
BasicMessageChannel<Object> channel =
965+
new BasicMessageChannel<>(
966+
binaryMessenger,
967+
"dev.flutter.pigeon.WebViewHostApi.setBackgroundColor",
968+
getCodec());
969+
if (api != null) {
970+
channel.setMessageHandler(
971+
(message, reply) -> {
972+
Map<String, Object> wrapped = new HashMap<>();
973+
try {
974+
ArrayList<Object> args = (ArrayList<Object>) message;
975+
Number instanceIdArg = (Number) args.get(0);
976+
if (instanceIdArg == null) {
977+
throw new NullPointerException("instanceIdArg unexpectedly null.");
978+
}
979+
Number colorArg = (Number) args.get(1);
980+
if (colorArg == null) {
981+
throw new NullPointerException("colorArg unexpectedly null.");
982+
}
983+
api.setBackgroundColor(instanceIdArg.longValue(), colorArg.longValue());
984+
wrapped.put("result", null);
985+
} catch (Error | RuntimeException exception) {
986+
wrapped.put("error", wrapError(exception));
987+
}
988+
reply.reply(wrapped);
989+
});
990+
} else {
991+
channel.setMessageHandler(null);
992+
}
993+
}
961994
}
962995
}
963996

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,12 @@ public void setWebChromeClient(Long instanceId, Long clientInstanceId) {
502502
webView.setWebChromeClient((WebChromeClient) instanceManager.getInstance(clientInstanceId));
503503
}
504504

505+
@Override
506+
public void setBackgroundColor(Long instanceId, Long color) {
507+
final WebView webView = (WebView) instanceManager.getInstance(instanceId);
508+
webView.setBackgroundColor(color.intValue());
509+
}
510+
505511
@Nullable
506512
private static String parseNullStringIdentifier(String value) {
507513
if (value.equals(nullStringIdentifier)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutterexample;
6+
7+
import static androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget;
8+
import static androidx.test.espresso.flutter.action.FlutterActions.click;
9+
import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withText;
10+
import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withValueKey;
11+
import static org.junit.Assert.assertEquals;
12+
13+
import android.graphics.Bitmap;
14+
import android.graphics.Color;
15+
import androidx.test.core.app.ActivityScenario;
16+
import androidx.test.ext.junit.runners.AndroidJUnit4;
17+
import androidx.test.rule.ActivityTestRule;
18+
import androidx.test.runner.screenshot.ScreenCapture;
19+
import androidx.test.runner.screenshot.Screenshot;
20+
import org.junit.Before;
21+
import org.junit.Ignore;
22+
import org.junit.Rule;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
26+
@RunWith(AndroidJUnit4.class)
27+
public class BackgroundColorTest {
28+
@Rule
29+
public ActivityTestRule<DriverExtensionActivity> myActivityTestRule =
30+
new ActivityTestRule<>(DriverExtensionActivity.class, true, false);
31+
32+
@Before
33+
public void setUp() {
34+
ActivityScenario.launch(DriverExtensionActivity.class);
35+
}
36+
37+
@Ignore("Doesn't run in Firebase Test Lab: https://github.com/flutter/flutter/issues/94748")
38+
@Test
39+
public void backgroundColor() {
40+
onFlutterWidget(withValueKey("ShowPopupMenu")).perform(click());
41+
onFlutterWidget(withValueKey("ShowTransparentBackgroundExample")).perform(click());
42+
onFlutterWidget(withText("Transparent background test"));
43+
44+
final ScreenCapture screenCapture = Screenshot.capture();
45+
final Bitmap screenBitmap = screenCapture.getBitmap();
46+
47+
final int centerLeftColor =
48+
screenBitmap.getPixel(10, (int) Math.floor(screenBitmap.getHeight() / 2.0));
49+
final int centerColor =
50+
screenBitmap.getPixel(
51+
(int) Math.floor(screenBitmap.getWidth() / 2.0),
52+
(int) Math.floor(screenBitmap.getHeight() / 2.0));
53+
54+
// Flutter Colors.green color : 0xFF4CAF50
55+
// https://github.com/flutter/flutter/blob/f4abaa0735eba4dfd8f33f73363911d63931fe03/packages/flutter/lib/src/material/colors.dart#L1208
56+
// The background color of the webview is : rgba(0, 0, 0, 0.5)
57+
// The expected color is : rgba(38, 87, 40, 1) -> 0xFF265728
58+
assertEquals(0xFF265728, centerLeftColor);
59+
assertEquals(Color.RED, centerColor);
60+
}
61+
}

packages/webview_flutter/webview_flutter_android/example/android/app/src/debug/AndroidManifest.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,13 @@
1313
android:hardwareAccelerated="true"
1414
android:windowSoftInputMode="adjustResize">
1515
</activity>
16+
<activity
17+
android:name=".DriverExtensionActivity"
18+
android:launchMode="singleTop"
19+
android:theme="@style/LaunchTheme"
20+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
21+
android:hardwareAccelerated="true"
22+
android:windowSoftInputMode="adjustResize">
23+
</activity>
1624
</application>
1725
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.webviewflutterexample;
6+
7+
import androidx.annotation.NonNull;
8+
import io.flutter.embedding.android.FlutterActivity;
9+
10+
public class DriverExtensionActivity extends FlutterActivity {
11+
@Override
12+
@NonNull
13+
public String getDartEntrypointFunctionName() {
14+
return "appMain";
15+
}
16+
}

packages/webview_flutter/webview_flutter_android/example/lib/main.dart

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'dart:convert';
99
import 'dart:io';
1010
import 'package:flutter/foundation.dart';
1111
import 'package:flutter/material.dart';
12+
import 'package:flutter_driver/driver_extension.dart';
1213
import 'package:path_provider/path_provider.dart';
1314
import 'package:webview_flutter_android/webview_surface_android.dart';
1415
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
@@ -17,6 +18,11 @@ import 'navigation_decision.dart';
1718
import 'navigation_request.dart';
1819
import 'web_view.dart';
1920

21+
void appMain() {
22+
enableFlutterDriverExtension();
23+
main();
24+
}
25+
2026
void main() {
2127
// Configure the [WebView] to use the [SurfaceAndroidWebView]
2228
// implementation instead of the default [AndroidWebView].
@@ -59,6 +65,27 @@ const String kExamplePage = '''
5965
</html>
6066
''';
6167

68+
const String kTransparentBackgroundPage = '''
69+
<!DOCTYPE html>
70+
<html>
71+
<head>
72+
<title>Transparent background test</title>
73+
</head>
74+
<style type="text/css">
75+
body { background: transparent; margin: 0; padding: 0; }
76+
#container { position: relative; margin: 0; padding: 0; width: 100vw; height: 100vh; }
77+
#shape { background: #FF0000; width: 200px; height: 100%; margin: 0; padding: 0; position: absolute; top: 0; bottom: 0; left: calc(50% - 100px); }
78+
p { text-align: center; }
79+
</style>
80+
<body>
81+
<div id="container">
82+
<p>Transparent background test</p>
83+
<div id="shape"></div>
84+
</div>
85+
</body>
86+
</html>
87+
''';
88+
6289
class _WebViewExample extends StatefulWidget {
6390
const _WebViewExample({Key? key}) : super(key: key);
6491

@@ -73,6 +100,7 @@ class _WebViewExampleState extends State<_WebViewExample> {
73100
@override
74101
Widget build(BuildContext context) {
75102
return Scaffold(
103+
backgroundColor: const Color(0xFF4CAF50),
76104
appBar: AppBar(
77105
title: const Text('Flutter WebView example'),
78106
// This drop down menu demonstrates that Flutter widgets can be shown over the web view.
@@ -109,6 +137,7 @@ class _WebViewExampleState extends State<_WebViewExample> {
109137
javascriptChannels: _createJavascriptChannels(context),
110138
javascriptMode: JavascriptMode.unrestricted,
111139
userAgent: 'Custom_User_Agent',
140+
backgroundColor: const Color(0x80000000),
112141
);
113142
}),
114143
floatingActionButton: favoriteButton(),
@@ -158,6 +187,7 @@ enum _MenuOptions {
158187
navigationDelegate,
159188
loadLocalFile,
160189
loadHtmlString,
190+
transparentBackground,
161191
}
162192

163193
class _SampleMenu extends StatelessWidget {
@@ -172,6 +202,7 @@ class _SampleMenu extends StatelessWidget {
172202
builder:
173203
(BuildContext context, AsyncSnapshot<WebViewController> controller) {
174204
return PopupMenuButton<_MenuOptions>(
205+
key: const ValueKey<String>('ShowPopupMenu'),
175206
onSelected: (_MenuOptions value) {
176207
switch (value) {
177208
case _MenuOptions.showUserAgent:
@@ -201,6 +232,9 @@ class _SampleMenu extends StatelessWidget {
201232
case _MenuOptions.loadHtmlString:
202233
_onLoadHtmlStringExample(controller.data!, context);
203234
break;
235+
case _MenuOptions.transparentBackground:
236+
_onTransparentBackground(controller.data!, context);
237+
break;
204238
}
205239
},
206240
itemBuilder: (BuildContext context) => <PopupMenuItem<_MenuOptions>>[
@@ -241,6 +275,11 @@ class _SampleMenu extends StatelessWidget {
241275
value: _MenuOptions.loadLocalFile,
242276
child: Text('Load local file'),
243277
),
278+
const PopupMenuItem<_MenuOptions>(
279+
key: ValueKey<String>('ShowTransparentBackgroundExample'),
280+
value: _MenuOptions.transparentBackground,
281+
child: Text('Transparent background example'),
282+
),
244283
],
245284
);
246285
},
@@ -353,6 +392,11 @@ class _SampleMenu extends StatelessWidget {
353392

354393
return indexFile.path;
355394
}
395+
396+
Future<void> _onTransparentBackground(
397+
WebViewController controller, BuildContext context) async {
398+
await controller.loadHtmlString(kTransparentBackgroundPage);
399+
}
356400
}
357401

358402
class _NavigationControls extends StatelessWidget {

packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class WebView extends StatefulWidget {
7676
this.initialMediaPlaybackPolicy =
7777
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
7878
this.allowsInlineMediaPlayback = false,
79+
this.backgroundColor,
7980
}) : assert(javascriptMode != null),
8081
assert(initialMediaPlaybackPolicy != null),
8182
assert(allowsInlineMediaPlayback != null),
@@ -236,6 +237,12 @@ class WebView extends StatefulWidget {
236237
/// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types].
237238
final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy;
238239

240+
/// The background color of the [WebView].
241+
///
242+
/// When `null` the platform's webview default background color is used. By
243+
/// default [backgroundColor] is `null`.
244+
final Color? backgroundColor;
245+
239246
@override
240247
_WebViewState createState() => _WebViewState();
241248
}
@@ -287,6 +294,7 @@ class _WebViewState extends State<WebView> {
287294
_javascriptChannelRegistry.channels.keys.toSet(),
288295
autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy,
289296
userAgent: widget.userAgent,
297+
backgroundColor: widget.backgroundColor,
290298
),
291299
javascriptChannelRegistry: _javascriptChannelRegistry,
292300
);

packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:ui';
6+
57
import 'package:flutter/foundation.dart';
68
import 'package:flutter/widgets.dart' show AndroidViewSurface;
79

@@ -349,6 +351,11 @@ class WebView {
349351
return api.setWebChromeClientFromInstance(this, client);
350352
}
351353

354+
/// Sets the background color of this WebView.
355+
Future<void> setBackgroundColor(Color color) {
356+
return api.setBackgroundColorFromInstance(this, color.value);
357+
}
358+
352359
/// Releases all resources used by the [WebView].
353360
///
354361
/// Any methods called after [release] will throw an exception.

packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,31 @@ class WebViewHostApi {
708708
return;
709709
}
710710
}
711+
712+
Future<void> setBackgroundColor(int arg_instanceId, int arg_color) async {
713+
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
714+
'dev.flutter.pigeon.WebViewHostApi.setBackgroundColor', codec,
715+
binaryMessenger: _binaryMessenger);
716+
final Map<Object?, Object?>? replyMap = await channel
717+
.send(<Object>[arg_instanceId, arg_color]) as Map<Object?, Object?>?;
718+
if (replyMap == null) {
719+
throw PlatformException(
720+
code: 'channel-error',
721+
message: 'Unable to establish connection on channel.',
722+
details: null,
723+
);
724+
} else if (replyMap['error'] != null) {
725+
final Map<Object?, Object?> error =
726+
(replyMap['error'] as Map<Object?, Object?>?)!;
727+
throw PlatformException(
728+
code: (error['code'] as String?)!,
729+
message: error['message'] as String?,
730+
details: error['details'],
731+
);
732+
} else {
733+
return;
734+
}
735+
}
711736
}
712737

713738
class _WebSettingsHostApiCodec extends StandardMessageCodec {

0 commit comments

Comments
 (0)