Flutter内嵌网页插件flutter_iframe_webview的使用

Flutter内嵌网页插件flutter_iframe_webview的使用

功能

一个运行在Flutter Web上的WebView组件,它是基于 webview_flutter_web 插件修改的,实现了PlatformWebViewController抽象类的所有方法,抹平了所有方法在全平台的调用异常,额外针对部分方法实现了相应的功能。

此插件主要针对如何在flutter web与web中通信交互提供了一种解决方案,因为该插件是基于iframe模拟了一个webview,受制于浏览器的同源策略,不同域之间通信是会出现跨域问题的,最好的解决方式是通过postMessage去实现通信。此外,借助于 flutter_jsbridge_sdk 插件,我们制定了统一的规范在flutter web与web进行通信。

方法 是否实现 说明
loadFile 否 平台不支持
loadFlutterAsset 否 平台不支持
loadHtmlString 是
loadRequest 是
currentUrl 是
canGoBack 否 平台不支持
canGoForward 否 平台不支持
goBack 是
reload 是
clearCache 是
clearLocalStorage 是
setPlatformNavigationDelegate 是 部分实现(仅onPageFinished方法)
runJavaScript 是
runJavaScriptReturningResult 是
addJavaScriptChannel 是 仅支持添加jsBridge.channelName
removeJavaScriptChannel 是
getTitle 是
scrollTo 是
scrollBy 是
getScrollPosition 是
enableZoom 否 平台不支持
setBackgroundColor 是
setJavaScriptMode 是
setUserAgent 否 平台不支持

开始

在工程的pubspec.yaml文件中添加flutter_jsbridge_sdk插件

dependencies:
  flutter_iframe_webview: ^1.0.2
  flutter_jsbridge_sdk: ^1.0.1

导入相关的包:

import 'package:flutter_iframe_webview/webview_flutter_web.dart';
import 'package:flutter_jsbridge_sdk/flutter_jsbridge_sdk.dart';

Flutter配置

WebViewController
late final PlatformWebViewController platform;
if (WebViewPlatform.instance is WebWebViewPlatform) {
  platform = WebWebViewController(WebWebViewControllerCreationParams());
} else {
  platform = PlatformWebViewController(
  const PlatformWebViewControllerCreationParams());
}
final WebViewController controller = WebViewController.fromPlatform(platform);
controller
    ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            debugPrint('WebView is loading (progress : $progress%)');
          },
          onPageStarted: (String url) {
            debugPrint('Page started loading: $url');
          },
          onPageFinished: (String url) {
            debugPrint('Page finished loading: $url');
          },
          onWebResourceError: (WebResourceError error) {
            debugPrint('''
Page resource error:
  code: ${error.errorCode}
  description: ${error.description}
  errorType: ${error.errorType}
  isForMainFrame: ${error.isForMainFrame}
          ''');
          },
          onNavigationRequest: (NavigationRequest request) {
            if (request.url.startsWith('https://www.youtube.com/')) {
              debugPrint('blocking navigation to ${request.url}');
              return NavigationDecision.prevent;
            }
            debugPrint('allowing navigation to ${request.url}');
            return NavigationDecision.navigate;
          },
        ),
      )
      ..addJavaScriptChannel(
        jsBridge.channelName,
        onMessageReceived: (JavaScriptMessage message) {
          jsBridge.onMessageReceived(message.message);
        },
      )
      ..addJavaScriptChannel(
        'Toaster',
        onMessageReceived: (JavaScriptMessage message) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text(message.message)),
          );
        },
      )
      ..loadRequest(Uri.parse('http://192.168.101.143:5500/page1.html'));
JavaScriptChannel
controller.addJavaScriptChannel(
    jsBridge.channelName,
    onMessageReceived: (JavaScriptMessage message) {
      jsBridge.onMessageReceived(message.message);
    },
  );
jsBridge
jsBridge.init(
  messageExecutor: (controller.platform as WebWebViewController).postMessage,
  debug: true,
);

JS配置

为了正常通信,web端还需引入javascript-jsbridge-sdk库,

具体参考 javascript-jsbridge-sdk

如何通信

关于如何在flutter_web与web中通信,请参考 flutter_jsbridge_sdk


完整示例demo

以下是一个完整的示例代码,展示了如何在Flutter应用中使用flutter_iframe_webview插件来加载网页,并实现与Web端的通信。

import 'dart:convert';

import 'package:example/webview_controller_delegate/webview_controller_delegate.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_jsbridge_sdk/flutter_jsbridge_sdk.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

const String kNavigationExamplePage = '''
<!DOCTYPE html><html>
<head><title>Navigation Delegate Example</title></head>
<body>
<p>
The navigation delegate is set to block navigation to the youtube website.
</p>
<ul>
<ul><a href="https://www.youtube.com/">https://www.youtube.com/</a></ul>
<ul><a href="https://www.google.cn/">https://www.google.cn/</a></ul>
</ul>
</body>
</html>
''';

const String kLocalExamplePage = '''
<!DOCTYPE html>
<html lang="en">
<head>
<title>Load file or HTML string example</title>
</head>
<body>

<h1>Local demo page</h1>
<p>
  This is an example page used to demonstrate how to load a local file or HTML
  string using the <a href="https://pub.flutter-io.cn/packages/webview_flutter">Flutter
  webview</a> plugin.
</p>

</body>
</html>
''';

const String kTransparentBackgroundPage = '''
  <!DOCTYPE html>
  <html>
  <head>
    <title>Transparent background test</title>
  </head>
  <style type="text/css">
    body { background: transparent; margin: 0; padding: 0; }
    #container { position: relative; margin: 0; padding: 0; width: 100vw; height: 100vh; }
    #shape { background: red; width: 200px; height: 200px; margin: 0; padding: 0; position: absolute; top: calc(50% - 100px); left: calc(50% - 100px); }
    p { text-align: center; }
  </style>
  <body>
    <div id="container">
      <p>Transparent background test</p>
      <div id="shape"></div>
    </div>
  </body>
  </html>
''';

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late final WebViewControllerDelegate _controller;

  [@override](/user/override)
  void initState() {
    super.initState();

    final WebViewControllerDelegate controller = WebViewControllerDelegate()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            debugPrint('WebView is loading (progress : $progress%)');
          },
          onPageStarted: (String url) {
            debugPrint('Page started loading: $url');
          },
          onPageFinished: (String url) {
            debugPrint('Page finished loading: $url');
          },
          onWebResourceError: (WebResourceError error) {
            debugPrint('''
Page resource error:
  code: ${error.errorCode}
  description: ${error.description}
  errorType: ${error.errorType}
  isForMainFrame: ${error.isForMainFrame}
          ''');
          },
          onNavigationRequest: (NavigationRequest request) {
            if (request.url.startsWith('https://www.youtube.com/')) {
              debugPrint('blocking navigation to ${request.url}');
              return NavigationDecision.prevent;
            }
            debugPrint('allowing navigation to ${request.url}');
            return NavigationDecision.navigate;
          },
        ),
      )
      ..addJavaScriptChannel(
        jsBridge.channelName,
        onMessageReceived: (JavaScriptMessage message) {
          jsBridge.onMessageReceived(message.message);
        },
      )
      ..addJavaScriptChannel(
        'Toaster',
        onMessageReceived: (JavaScriptMessage message) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text(message.message)),
          );
        },
      )
      ..loadRequest(Uri.parse('http://192.168.101.143:5500/page1.html'));

    _controller = controller;

    jsBridge.init(
      messageExecutor: _controller.messageExecutor,
      debug: true,
    );
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WebView'),
        actions: <Widget>[NavigationControls(webViewController: _controller)],
      ),
      body: Column(
        children: <Widget>[
          Expanded(child: WebViewWidget(controller: _controller)),
          SampleMenu(webViewController: _controller),
        ],
      ),
    );
  }

  Widget favoriteButton() {
    return FloatingActionButton(
      onPressed: () async {
        final String? url = await _controller.currentUrl();
        if (context.mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Favorited $url')),
          );
        }
      },
      child: const Icon(Icons.favorite),
    );
  }
}

class SampleMenu extends StatelessWidget {
  SampleMenu({
    super.key,
    required this.webViewController,
  });

  final WebViewController webViewController;
  late final WebViewCookieManager cookieManager = WebViewCookieManager();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ColoredBox(
      color: Colors.grey.withOpacity(0.3),
      child: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: Row(
          children: <Widget>[
            TextButton(
              onPressed: () {
                _onShowUserAgent(context);
              },
              child: const Text('Show user agent'),
            ),
            TextButton(
              onPressed: () {
                _onSetCookie();
              },
              child: const Text('Set cookie'),
            ),
            TextButton(
              onPressed: () {
                _onListCookies(context);
              },
              child: const Text('List cookies'),
            ),
            TextButton(
              onPressed: () {
                _onClearCookies(context);
              },
              child: const Text('Clear cookies'),
            ),
            TextButton(
              onPressed: () {
                _onAddToCache(context);
              },
              child: const Text('Add to cache'),
            ),
            TextButton(
              onPressed: () {
                _onListCache(context);
              },
              child: const Text('List cache'),
            ),
            TextButton(
              onPressed: () {
                _onClearCache(context);
              },
              child: const Text('Clear Cache'),
            ),
            TextButton(
              onPressed: () {
                _onNavigationDelegateExample();
              },
              child: const Text('Navigation Delegate example'),
            ),
            TextButton(
              onPressed: () {
                _onDoPostRequest();
              },
              child: const Text('Post Request'),
            ),
            TextButton(
              onPressed: () {
                _onLoadHtmlStringExample();
              },
              child: const Text('Load HTML string'),
            ),
            TextButton(
              onPressed: () {
                _onLoadLocalFileExample();
              },
              child: const Text('Load local file'),
            ),
            TextButton(
              onPressed: () {
                _onLoadFlutterAssetExample();
              },
              child: const Text('Load Flutter Asset'),
            ),
            TextButton(
              onPressed: () {
                _onTransparentBackground();
              },
              child: const Text('Transparent background example'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _onShowUserAgent(BuildContext context) async {
    final String userAgent = await webViewController
        .runJavaScriptReturningResult('navigator.userAgent') as String;
    if (context.mounted) {
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text(userAgent)));
    }
  }

  Future<void> _onListCookies(BuildContext context) async {
    final String cookies = await webViewController
        .runJavaScriptReturningResult('document.cookie') as String;
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            const Text('Cookies:'),
            _getCookieList(cookies),
          ],
        ),
      ));
    }
  }

  Future<void> _onAddToCache(BuildContext context) async {
    await webViewController.runJavaScript(
      'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";',
    );
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
        content: Text('Added a test entry to cache.'),
      ));
    }
  }

  Future<void> _onListCache(BuildContext context) async {
    final String caches = await webViewController.runJavaScriptReturningResult(
        'caches.keys()'
        // ignore: missing_whitespace_between_adjacent_strings
        '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
        '.then((caches) => console.error(caches))') as String;
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: Text(caches),
      ));
    }

    return webViewController.runJavaScript('caches.keys()'
        // ignore: missing_whitespace_between_adjacent_strings
        '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))'
        '.then((caches) => Toaster.postMessage(caches))');
  }

  Future<void> _onClearCache(BuildContext context) async {
    await webViewController.clearCache();
    await webViewController.clearLocalStorage();
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
        content: Text('Cache cleared.'),
      ));
    }
  }

  Future<void> _onClearCookies(BuildContext context) async {
    if (kIsWeb) {
      webViewController.runJavaScript("document.cookie = ''");
      return;
    }
    final bool hadCookies = await cookieManager.clearCookies();
    String message = 'There were cookies. Now, they are gone!';
    if (!hadCookies) {
      message = 'There are no cookies.';
    }
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: Text(message),
      ));
    }
  }

  Future<void> _onNavigationDelegateExample() {
    final String contentBase64 = base64Encode(
      const Utf8Encoder().convert(kNavigationExamplePage),
    );
    return webViewController.loadRequest(
      Uri.parse('data:text/html;base64,$contentBase64'),
    );
  }

  Future<void> _onSetCookie() async {
    if (kIsWeb) {
      await webViewController.runJavaScript(
          "document.cookie = '${Uri.encodeComponent('foo')}=${Uri.encodeComponent('bar')}; path=/anything;'");
    } else {
      await cookieManager.setCookie(
        const WebViewCookie(
          name: 'foo',
          value: 'bar',
          domain: 'httpbin.org',
          path: '/anything',
        ),
      );
    }
    await webViewController.loadRequest(Uri.parse(
      'https://httpbin.org/anything',
    ));
  }

  Future<void> _onDoPostRequest() {
    return webViewController.loadRequest(
      Uri.parse('https://httpbin.org/post'),
      method: LoadRequestMethod.post,
      headers: {'foo': 'bar', 'Content-Type': 'text/plain'},
      body: Uint8List.fromList('Test Body'.codeUnits),
    );
  }

  Future<void> _onLoadLocalFileExample() async {
    final String pathToIndex = await _prepareLocalFile();
    await webViewController.loadFile(pathToIndex);
  }

  Future<void> _onLoadFlutterAssetExample() {
    return webViewController.loadFlutterAsset('assets/www/index.html');
  }

  Future<void> _onLoadHtmlStringExample() {
    return webViewController.loadHtmlString(kLocalExamplePage);
  }

  Future<void> _onTransparentBackground() {
    return webViewController.loadHtmlString(kTransparentBackgroundPage);
  }

  Widget _getCookieList(String cookies) {
    if (cookies == '""') {
      return Container();
    }
    final List<String> cookieList = cookies.split(';');
    final Iterable<Text> cookieWidgets = cookieList.map((String cookie) => Text(cookie));
    return Column(
      mainAxisAlignment: MainAxisAlignment.end,
      mainAxisSize: MainAxisSize.min,
      children: cookieWidgets.toList(),
    );
  }

  static Future<String> _prepareLocalFile() async {
    return '';
    // final String tmpDir = (await getTemporaryDirectory()).path;
    // final File indexFile = File(
    //     <String>{tmpDir, 'www', 'index.html'}.join(Platform.pathSeparator));
    //
    // await indexFile.create(recursive: true);
    // await indexFile.writeAsString(kLocalExamplePage);
    //
    // return indexFile.path;
  }
}

class NavigationControls extends StatelessWidget {
  const NavigationControls({super.key, required this.webViewController});

  final WebViewController webViewController;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        IconButton(
          icon: const Icon(Icons.arrow_back_ios),
          onPressed: () async {
            if (await webViewController.canGoBack()) {
              await webViewController.goBack();
            } else {
              if (context.mounted) {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('No back history item')),
                );
              }
            }
          },
        ),
        IconButton(
          icon: const Icon(Icons.arrow_forward_ios),
          onPressed: () async {
            if (await webViewController.canGoForward()) {
              await webViewController.goForward();
            } else {
              if (context.mounted) {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('No forward history item')),
                );
              }
            }
          },
        ),
        IconButton(
          icon: const Icon(Icons.replay),
          onPressed: () => webViewController.reload(),
        ),
      ],
    );
  }
}

更多关于Flutter内嵌网页插件flutter_iframe_webview的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter内嵌网页插件flutter_iframe_webview的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter中使用flutter_iframe_webview插件来内嵌网页的示例代码。flutter_iframe_webview插件允许你在Flutter应用中嵌入和显示网页内容。不过需要注意的是,flutter_iframe_webview并不是Flutter社区中非常常见或广泛使用的插件,通常更常用的插件是webview_flutter或flutter_inappwebview。但为了满足你的要求,我将假设flutter_iframe_webview插件存在并具有类似的API。

首先,你需要在你的pubspec.yaml文件中添加flutter_iframe_webview依赖(注意:这个包名是我假设的,实际使用时请替换为真实的包名):

dependencies:
  flutter:
    sdk: flutter
  flutter_iframe_webview: ^x.y.z  # 替换为实际的版本号

然后,运行flutter pub get来安装依赖。

接下来,在你的Flutter应用中,你可以按照以下方式使用flutter_iframe_webview:

import 'package:flutter/material.dart';
import 'package:flutter_iframe_webview/flutter_iframe_webview.dart'; // 假设这是正确的导入路径

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: WebViewExample(),
    );
  }
}

class WebViewExample extends StatefulWidget {
  @override
  _WebViewExampleState createState() => _WebViewExampleState();
}

class _WebViewExampleState extends State<WebViewExample> {
  final WebViewController _controller = WebViewController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter WebView Example'),
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: IFrameWebView(
              initialUrl: 'https://www.example.com', // 你要加载的网页URL
              controller: _controller,
              onWebViewCreated: (WebViewController webViewController) {
                // 可以在这里保存WebViewController的实例,用于后续的操作,比如加载新的URL
                _controller = webViewController;
              },
              javascriptMode: JavascriptMode.unrestricted, // 是否允许执行JavaScript
            ),
          ),
          ElevatedButton(
            onPressed: () {
              // 示例:通过控制器加载新的URL
              _controller.loadUrl('https://www.google.com');
            },
            child: Text('Load Google'),
          ),
        ],
      ),
    );
  }
}

在这个示例中:

  1. 我们定义了一个WebViewExample的StatefulWidget,并在其State中创建了一个WebViewController实例。
  2. 使用IFrameWebView组件来嵌入网页,并设置了初始URL、控制器以及网页创建时的回调。
  3. 提供了一个按钮,通过控制器加载新的URL。

请注意,这个示例是基于假设的flutter_iframe_webview插件的API。如果实际插件的API不同,你可能需要调整代码。对于真实项目,建议使用社区广泛验证和支持的插件,如webview_flutter或flutter_inappwebview。

回到顶部