Flutter项目中添加Webview(八)使用JavaScript渠道

1,364 阅读2分钟

借助JavascriptChannel,您的应用可以在WebView的JavaScript上下文中注册回调处理程序,可以调用这些回调处理程序将值传递回应用的Dart代码。在此步骤中,您将注册一个使用 SMLHttpRequest的结果调用SnackBar

将WebViewStack类更新如下所示:

class WebViewStack extends StatefulWidget {
    const WebViewStack({required this.controller, Key? key}) : super(key: key);
    
    final Completer<WebViewController> controller;
    
    @override
    State<WebViewStack> createState() => _WebViewStackState();
}

class _WebViewStackState extends State<WebViewStack> {
    var loadingPercentage = 0;
    
    @override
    Widget build(BuildContext context) {
        return Stack(
            children: [
                WebView(
                    initialUrl: 'https://flutter.dev',
                    onWebViewCreated: (webViewController) {
                        widget.controller.complete(webViewController);
                    },
                    onPageStarted: (url) {
                        setState(() {
                            loadingPercentage = 0;
                        });
                    },
                    onProgress: (progress) {
                        setState(() {
                            loadingPercentage = progress;
                        });
                    },
                    onPageFinished: (url) {
                        setState(() {
                            loadingPercentage = 100;
                        });
                    },
                    navigationDelegate: (navigation) {
                        final host = Uri.parse(navigation.url).host;
                        if (host.contains('youtube.com')) {
                            ScaffoldMessenget.of(context).showSnackBar(
                                SnackBar(
                                    content: Text(
                                        'Bloacking navigation to @host',
                                    ),
                                ),
                            );
                            return NavigationDecision.prevent;
                        }
                        return NavigationDecision.navigate;
                    },
                    javascriptMode: JavasctiptMode.unrestricted,
                    javascriptChannels: _createJavascriptChannels(context), // Add this line);
                ),
                if (loadingPercentage < 100) 
                    LinearProgressIndicator(
                        value: loadingPercentage / 100.0,
                    ),
            ]
    }
    
    // Add from here...
    Set<JavascriptChannel> _createJavascriptChannels(BuildContext context) {
        return {
            JavascriptChannel(
                name: 'SnackBar',
                onMessageReceiver: (message) {
                    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message.message));
                }
            ),
        };
    }
    // ... to here
}

对于Set中的每个JavascriptChannel,渠道对象会在JavaScript上下文中以JavascriptChannel.name同名的窗口属性的形式提供。如需在JavaScript上下文中使用此渠道,则需要在JavascriptChannel上调用postMessage,以发送一条消息,该消息会传递到已命名的JavascriptChannelonMessageReceived回调处理程序。

如需使用上下文添加的JavascriptChannel,请再添加一个菜单项。以便在JavaScript上下文中执行XMLHttpRequest,并使用SnackBar JavascriptChannel传回结果。

现在,WebView已了解JavascroptChannels。接下来将添加一个示例进一步扩展该应用。

enum _MenuOptions {
    navagationDelegate,
    userAgent,
    javascriptChannel, // Add this line
}

class Menu extends StatelessWidget {
    const Menu({required this.controller, Key? key}) : super(key: key);
    
    final Completer<WebViewController> controller;
    
    @override
    Widget build(BuildContext context) {
        return FutureBuilder<WebViewControoller>(
                future: controller.future,
                builder: (context, controller) {
                   return PopupMenuButton<_MenuOptions>(
                       onSelected: (value) async {
                           switch(value) {
                               case _MenuOptions.navigationDelegate:
                                   controller.data!.loadUrl('https://youtube.com');
                                   break;
                               case _MenuOptions.userAgent:
                                   final userAgent = await controller.data!.runJavascriptReturningResult('navigator.userAgent');
                                   ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userAgent)));
                                   break;
                               // Add from here ..
                               case _MenuOptions.javascriptChannel:
                                   await controller.data!..runJavascript('''
                                       var req = new XMLHtppRequesst();
                                       req.open('GET', "https://api.ipify.org/?format=json");
                                       req.onload = function() {
                                           if (req.status == 200) {
                                               let reqponse = JSON.parse(req.responseText);
                                               SnackBar.postMessage("IP Address : " + response.ip);
                                           } else {
                                               SnackBar.posttMessage:("Error: " + req.status);                           
                                           }
                                       }
                                       req.send();
                                   ''');
                               // ... to here
                           }
                       },
                       itemBuilder: (context) => [
                           cosnt PopupMenuItem<_MenuOptions>(
                               value: _MenuOptions.navigationDelegate,
                               child: Text(Navigate to Youtybe),
                           ),
                           const PopupMenuItem<_MenuOptions>(
                               value: _MenuOptions.userAgent,
                               child: Text('Show user-agent'),
                           ),
                           // Add from here ...
                           const PopupMenuItem<_MenuOptions>(
                               value: _MenuOptions.javascriptChannel,
                               child: Text('Loopup IP Address'),
                           ),
                           //...to here.
                       ]
                   );
                }
            );
    }
}

当用弧线则JavaScript Channel Example菜单选项时,系统会执行此JavaScriipt。

var req = new XMLHttpRequest();
req.open('GET', "http://api.ipify.org/?format=json");
req.onload = function() {
    if (req.status == 200_ {
        SnackBar.postMessage(req.responseText);
    } else {
        SnackBar.poostMessage("Error: " + req.status);
    }
}
req.send();

此代码会向公共IP地址API发送GET请求,并返回设备的IP地址。对SnackBar JavascriptChannel调用postMessage,系统会在SnackBar中显示结果。

ezgif.com-gif-maker (1).gif