Flutter网页视图展示插件flutter_webview_plugin_ios_android的使用

Flutter网页视图展示插件flutter_webview_plugin_ios_android的使用

Flutter WebView 插件

Flutter Community: flutter_webview_plugin

关于

我是 Siddhant Parashar,我正在发布此软件包作为从 https://pub.dartlang.org/packages/flutter_webview_plugin 分叉出来的版本。

这个分叉解决了 Android 和 iOS 的两个主要问题:

  • 在 Android 上,已经修复了旧嵌入问题。
  • 在 iOS 上,webview 没有保留会话,这导致用户每次渲染 webview 时都会自动登出。这个问题在 iOS 上得到了重大修复。

赞助

请我喝杯咖啡吧! https://www.buymeacoffee.com/siddhant12p

关于插件

允许 Flutter 与原生 WebView 进行通信的插件。

注意:

WebView 不集成到小部件树中,它是一个位于 Flutter 视图之上的原生视图。 你将无法看到可能会与 webview 占用的屏幕区域重叠的 snackbars、对话框或其他 Flutter 小部件。

getSafeAcceptedType() 函数仅适用于最低 SDK 为 21。 eval() 函数仅支持 SDK 为 19 或更高版本以评估 JavaScript。

开始使用

有关如何开始使用 Flutter 的帮助,请参阅我们的在线文档 http://flutter.io/

iOS

为了使插件正常工作,你需要向 ios/Runner/Info.plist 添加新的键:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    <key>NSAllowsArbitraryLoadsInWebContent</key>
    <true/>
</dict>

NSAllowsArbitraryLoadsInWebContent 用于 iOS 10+,而 NSAllowsArbitraryLoads 用于 iOS 9。

它是如何工作的

全屏启动 WebView 并使用 Flutter 导航

new MaterialApp(
  routes: {
    "/": (_) => new WebviewScaffold(
      url: "https://www.google.com",
      appBar: new AppBar(
        title: new Text("Widget webview"),
      ),
    ),
  },
);

可选参数 hiddeninitialChild 可供使用,以便在页面加载期间显示其他内容。 如果设置 hidden 为 true,则会显示默认的 CircularProgressIndicator。如果你另外指定了一个 Widget 作为 initialChild,你可以显示任何你喜欢的内容直到页面加载完成。

例如,以下代码将显示一个带有文本 ‘waiting…’ 的红色屏幕。

return new MaterialApp(
  title: 'Flutter WebView Demo',
  theme: new ThemeData(
    primarySwatch: Colors.blue,
  ),
  routes: {
    '/': (_) => const MyHomePage(title: 'Flutter WebView Demo'),
    '/widget': (_) => new WebviewScaffold(
      url: selectedUrl,
      appBar: new AppBar(
        title: const Text('Widget webview'),
      ),
      withZoom: true,
      withLocalStorage: true,
      hidden: true,
      initialChild: Container(
        color: Colors.redAccent,
        child: const Center(
          child: Text('Waiting.....'),
        ),
      ),
    ),
  },
);

FlutterWebviewPlugin 提供一个与唯一 webview 相关联的单例实例,因此你可以在应用的任何地方控制 webview。

监听事件:

final flutterWebviewPlugin = new FlutterWebviewPlugin();

flutterWebviewPlugin.onUrlChanged.listen((String url) {

});

监听 WebView 中的滚动事件

final flutterWebviewPlugin = new FlutterWebviewPlugin();
flutterWebviewPlugin.onScrollYChanged.listen((double offsetY) { // 最新的垂直滚动偏移值
  // 在这里比较垂直滚动变化与旧值
});

flutterWebviewPlugin.onScrollXChanged.listen((double offsetX) { // 最新的水平滚动偏移值
  // 在这里比较水平滚动变化与旧值
});

注意:请注意,iOS 和 Android 之间的滚动距离略有不同。Android 的滚动值差异通常比 iOS 设备大。

隐藏 WebView

final flutterWebviewPlugin = new FlutterWebviewPlugin();

flutterWebviewPlugin.launch(url, hidden: true);

关闭已启动的 WebView

flutterWebviewPlugin.close();

在自定义矩形内显示 WebView

final flutterWebviewPlugin = new FlutterWebviewPlugin();

flutterWebviewPlugin.launch(url,
  fullScreen: false,
  rect: new Rect.fromLTWH(
    0.0,
    0.0,
    MediaQuery.of(context).size.width,
    300.0,
  ),
);

向 WebView 注入自定义代码

使用 flutterWebviewPlugin.evalJavaScript(String code)。这个函数必须在页面加载完成后运行(即监听 onStateChanged 事件,当状态为 finishLoad 时)。

如果你有大量的 JavaScript 需要嵌入,可以使用资源文件。将资源文件添加到 pubspec.yaml,然后像这样调用函数:

Future<String> loadJS(String name) async {
  var givenJS = rootBundle.loadString('assets/$name.js');
  return givenJS.then((String js) {
    flutterWebViewPlugin.onStateChanged.listen((viewState) async {
      if (viewState.type == WebViewState.finishLoad) {
        flutterWebViewPlugin.evalJavascript(js);
      }
    });
  });
}

访问文件系统中的本地文件

launch 函数或 Webview 架构中将 withLocalUrl 选项设置为 true 以启用对本地 URL 的支持。

注意,在 iOS 上,还需要将 localUrlScope 选项设置为目录路径。所有位于此文件夹(或子文件夹)内的文件都将被允许访问。如果省略,只有正在打开的本地文件将被允许访问,导致无法加载子资源。该选项在 Android 上被忽略。

忽略 SSL 错误

ignoreSSLErrors 选项设置为 true 以显示来自通常不受 WebView 信任的服务器(如自签名证书)的内容。

注意:

不要在生产环境中使用此功能。

注意,对于 iOS,你还需要向 ios/Runner/Info.plist 添加新的键:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    <key>NSAllowsArbitraryLoadsInWebContent</key>
    <true/>
</dict>

NSAllowsArbitraryLoadsInWebContent 用于 iOS 10+,而 NSAllowsArbitraryLoads 用于 iOS 9。 否则,你仍然无法显示来自具有不受信任证书的页面的内容。

你可以通过 https://self-signed.badssl.com/ 测试你的 SSL 证书忽略是否有效。

WebView 事件

  • Stream<Null> onDestroy
  • Stream<String> onUrlChanged
  • Stream<WebViewStateChanged> onStateChanged
  • Stream<double> onScrollXChanged
  • Stream<double> onScrollYChanged
  • Stream<String> onError

不要忘记释放 WebView

flutterWebviewPlugin.dispose()

WebView 函数

Future<Null> launch(String url, {
    Map<String, String> headers: null,
    Set<JavascriptChannel> javascriptChannels: null,
    bool withJavascript: true,
    bool clearCache: false,
    bool clearCookies: false,
    bool hidden: false,
    bool enableAppScheme: true,
    Rect rect: null,
    String userAgent: null,
    bool withZoom: false,
    bool displayZoomControls: false,
    bool withLocalStorage: true,
    bool withLocalUrl: true,
    String localUrlScope: null,
    bool withOverviewMode: false,
    bool scrollBar: true,
    bool supportMultipleWindows: false,
    bool appCacheEnabled: false,
    bool allowFileURLs: false,
    bool useWideViewPort: false,
    String invalidUrlRegex: null,
    bool geolocationEnabled: false,
    bool debuggingEnabled: false,
    bool ignoreSSLErrors: false,
});
Future<String> evalJavascript(String code);
Future<Map<String, dynamic>> getCookies();
Future<Null> cleanCookies();
Future<Null> resize(Rect rect);
Future<Null> show();
Future<Null> hide();
Future<Null> reloadUrl(String url);
Future<Null> close();
Future<Null> reload();
Future<Null> goBack();
Future<Null> goForward();
Future<Null> stopLoading();
Future<bool> canGoBack();
Future<bool> canGoForward();

示例代码

main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin_ios_android/flutter_webview_plugin_ios_android.dart';

const kAndroidUserAgent =
    'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36';

String selectedUrl = 'https://flutter.io';

// ignore: prefer_collection_literals
final Set<JavascriptChannel> jsChannels = [
  JavascriptChannel(
      name: 'Print',
      onMessageReceived: (message) {
        print(message.message);
      }),
].toSet();

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final flutterWebViewPlugin = FlutterWebviewPlugin();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter WebView Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      routes: {
        '/': (_) => MyHomePage(title: 'Flutter WebView Demo'),
        '/widget': (_) {
          return WebviewScaffold(
            url: selectedUrl,
            javascriptChannels: jsChannels,
            mediaPlaybackRequiresUserGesture: false,
            appBar: AppBar(
              title: const Text('Widget WebView'),
            ),
            withZoom: true,
            withLocalStorage: true,
            hidden: true,
            initialChild: Container(
              color: Colors.redAccent,
              child: const Center(
                child: Text('Waiting.....'),
              ),
            ),
            bottomNavigationBar: BottomAppBar(
              child: Row(
                children: <Widget>[
                  IconButton(
                    icon: const Icon(Icons.arrow_back_ios),
                    onPressed: () {
                      flutterWebViewPlugin.goBack();
                    },
                  ),
                  IconButton(
                    icon: const Icon(Icons.arrow_forward_ios),
                    onPressed: () {
                      flutterWebViewPlugin.goForward();
                    },
                  ),
                  IconButton(
                    icon: const Icon(Icons.autorenew),
                    onPressed: () {
                      flutterWebViewPlugin.reload();
                    },
                  ),
                ],
              ),
            ),
          );
        },
      },
    );
  }
}

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  // WebView 插件实例
  final flutterWebViewPlugin = FlutterWebviewPlugin();

  // 销毁流订阅
  late StreamSubscription _onDestroy;

  // URL 改变流订阅
  late StreamSubscription<String> _onUrlChanged;

  // 状态改变流订阅
  late StreamSubscription<WebViewStateChanged> _onStateChanged;

  late StreamSubscription<WebViewHttpError> _onHttpError;

  late StreamSubscription<double> _onProgressChanged;

  late StreamSubscription<double> _onScrollYChanged;

  late StreamSubscription<double> _onScrollXChanged;

  final _urlCtrl = TextEditingController(text: selectedUrl);

  final _codeCtrl = TextEditingController(text: 'window.navigator.userAgent');

  final _scaffoldKey = GlobalKey<ScaffoldState>();

  final _history = [];

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

    flutterWebViewPlugin.close();

    _urlCtrl.addListener(() {
      selectedUrl = _urlCtrl.text;
    });

    // 添加销毁 WebView 的监听器,以便你可以执行某些操作。
    _onDestroy = flutterWebViewPlugin.onDestroy.listen((_) {
      if (mounted) {
        // 执行一些动作,例如显示信息提示。
        ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: const Text('Webview Destroyed')));
      }
    });

    // 添加 URL 改变的监听器
    _onUrlChanged = flutterWebViewPlugin.onUrlChanged.listen((String url) {
      if (mounted) {
        setState(() {
          _history.add('onUrlChanged: $url');
        });
      }
    });

    _onProgressChanged =
        flutterWebViewPlugin.onProgressChanged.listen((double progress) {
      if (mounted) {
        setState(() {
          _history.add('onProgressChanged: $progress');
        });
      }
    });

    _onScrollYChanged =
        flutterWebViewPlugin.onScrollYChanged.listen((double y) {
      if (mounted) {
        setState(() {
          _history.add('Scroll in Y Direction: $y');
        });
      }
    });

    _onScrollXChanged =
        flutterWebViewPlugin.onScrollXChanged.listen((double x) {
      if (mounted) {
        setState(() {
          _history.add('Scroll in X Direction: $x');
        });
      }
    });

    _onStateChanged =
        flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state) {
      if (mounted) {
        setState(() {
          _history.add('onStateChanged: ${state.type} ${state.url}');
        });
      }
    });

    _onHttpError =
        flutterWebViewPlugin.onHttpError.listen((WebViewHttpError error) {
      if (mounted) {
        setState(() {
          _history.add('onHttpError: ${error.code} ${error.url}');
        });
      }
    });
  }

  [@override](/user/override)
  void dispose() {
    // 取消所有监听器,同样也需要取消这个流。
    _onDestroy.cancel();
    _onUrlChanged.cancel();
    _onStateChanged.cancel();
    _onHttpError.cancel();
    _onProgressChanged.cancel();
    _onScrollXChanged.cancel();
    _onScrollYChanged.cancel();

    flutterWebViewPlugin.dispose();

    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: const Text('Plugin example app'),
      ),
      body: SingleChildScrollView(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              padding: const EdgeInsets.all(24.0),
              child: TextField(controller: _urlCtrl),
            ),
            ElevatedButton(
              onPressed: () {
                flutterWebViewPlugin.launch(
                  selectedUrl,
                  geolocationEnabled: true,
                  rect: Rect.fromLTWH(
                      0.0, 0.0, MediaQuery.of(context).size.width, 300.0),
                  userAgent: kAndroidUserAgent,
                  invalidUrlRegex:
                      r'^(https).+(twitter)', // 防止点击 flutter 网站上的 twitter 图标时重定向到 twitter
                );
              },
              child: const Text('Open Webview (rect)'),
            ),
            ElevatedButton(
              onPressed: () {
                flutterWebViewPlugin.launch(
                  selectedUrl,
                  hidden: true,
                  geolocationEnabled: true,
                );
              },
              child: const Text('Open "hidden" Webview'),
            ),
            ElevatedButton(
              onPressed: () {
                flutterWebViewPlugin.launch(selectedUrl,
                  geolocationEnabled: true,

                );
              },
              child: const Text('Open Fullscreen Webview'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pushNamed('/widget');
              },
              child: const Text('Open widget webview'),
            ),
            Container(
              padding: const EdgeInsets.all(24.0),
              child: TextField(controller: _codeCtrl),
            ),
            ElevatedButton(
              onPressed: () {
                final future =
                    flutterWebViewPlugin.evalJavascript(_codeCtrl.text);
                future.then((result) {
                  setState(() {
                    _history.add('eval: $result');
                  });
                });
              },
              child: const Text('Eval some javascript'),
            ),
            ElevatedButton(
              onPressed: () {
                final future = flutterWebViewPlugin
                    .evalJavascript('alert("Hello World");');
                future.then((result) {
                  setState(() {
                    _history.add('eval: $result');
                  });
                });
              },
              child: const Text('Eval javascript alert()'),
            ),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _history.clear();
                });
                flutterWebViewPlugin.close();
              },
              child: const Text('Close'),
            ),
            ElevatedButton(
              onPressed: () {
                flutterWebViewPlugin.getCookies().then((m) {
                  setState(() {
                    _history.add('cookies: $m');
                  });
                });
              },
              child: const Text('Cookies'),
            ),
            Text(_history.join('\n'))
          ],
        ),
      ),
    );
  }
}

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

1 回复

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


flutter_webview_plugin_ios_android 是一个用于在 Flutter 应用中嵌入 WebView 的插件。它允许你在应用中展示网页内容,并且支持 iOS 和 Android 平台。虽然它可能不是官方推荐的 WebView 插件(官方推荐使用 webview_flutter 插件),但它仍然是一个可选的解决方案。

安装插件

首先,你需要在 pubspec.yaml 文件中添加 flutter_webview_plugin_ios_android 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_webview_plugin_ios_android: ^0.0.1  # 请检查最新版本

然后,运行 flutter pub get 来安装插件。

基本使用

以下是一个简单的示例,展示如何在 Flutter 应用中使用 flutter_webview_plugin_ios_android 插件来展示一个网页:

import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin_ios_android/flutter_webview_plugin_ios_android.dart';

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter WebView Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: WebViewExample(),
    );
  }
}

class WebViewExample extends StatelessWidget {
  final flutterWebviewPlugin = FlutterWebviewPlugin();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter WebView Example'),
      ),
      body: WebviewScaffold(
        url: 'https://www.example.com',
        withZoom: true,
        withLocalStorage: true,
        hidden: true,
        initialChild: Center(
          child: CircularProgressIndicator(),
        ),
      ),
    );
  }
}
回到顶部