Flutter内嵌网页插件flutter_iframe_webview的使用

发布于 1周前 作者 htzhanglong 来自 Flutter

Flutter内嵌网页插件flutter_iframe_webview的使用

功能

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

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

方法 是否实现 说明
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_webweb中通信,请参考 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_flutterflutter_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_flutterflutter_inappwebview

回到顶部