在 Flutter 中使用 webview_flutter 4.0 | 基础用法与事件处理

news2024/10/8 14:29:28

在这里插入图片描述

大家好,我是 17。

Flutter WebView 一共写了四篇文章

  1. 在 Flutter 中使用 webview_flutter 4.0 | 基础用法与事件处理
  2. 在 Flutter 中使用 webview_flutter 4.0 | js 交互
  3. Flutter WebView 性能优化,让 h5 像原生页面一样优秀,已入选 掘金一周 2023.02.22 期
  4. Flutter WebView 如何与 h5 同步登录状态 已入选 CSDN每天值得看–2023-02-21

本文是第 1 篇,定位是新手入门,介绍和演示 webview 的基础用法。最后还介绍了事件处理的技巧。

环境准备已经在 在 Flutter 中使用 webview_flutter 4.0 | js 交互 说过了,不再赘述。既然是新手入门,就会本着详尽的原则,而且会多配示例。

获取页面信息

获取页面 title

核心方法:controller.getTitle

完整示例,放在 main.dart 就能运行。运行示例,点击获取 title 的按钮,会在控制台显示:CSDN - 专业开发者社区

使用 WebView_flutter 4.0 主要分三步

  1. 声明 WebViewController
  2. 在 initState 中初始化 controller
  3. 把 controller 赋值给 WebViewWidget,WebViewWidget 显示页面。
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

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

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

  
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // 1
  late WebViewController controller;
  
  void initState() {
    // 2
    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse('https://www.csdn.net/'));
    super.initState();
  }

  
  Widget build(BuildContext context) {
   
    return MaterialApp(
        home: Scaffold(
            body: Column(
              children: [
                // 3
                Expanded(child: WebViewWidget(controller: controller))
              ],
            ),
            floatingActionButton: ElevatedButton(
              onPressed: () async {
                var title = await controller.getTitle();
                print(title);
              },
              child: Text('获取 title'),
            )));
  }
}

页面信息相关的还有另外三个方法:

  • currentUrl 获取页面的 url
  • reload 重新加载当前 url
  • setUserAgent 设置页面 userAgent

userAgent 用来标识身份。具体怎么写自己定,但要有一个特殊的标识用作区分。比如微信的标识是 MicroMessenger。

滚动页面

  • scrollBy 相对原位置滚动
  • scrollTo 滚动到绝对位置

比如现在滚动位置在 0 0,scrollBy(0,100) 执行一次向下滚动 100,执行两次向下滚动 200。scrollTo(0,100) 执行一次向下滚动 100,执行两次不动,还是在 0 100 的位置。

用 getScrollPosition 可以获得页面当前滚动位置。

这三个都是 controller 的方法和前面提到的 controller.getTitle 的用法是一样的。

虽然 controller 给出了三个方法,但还是略显不够。比如我想让页面向上滚动一半怎么办?还是得靠万能的 js 先获取页面的高度。

floatingActionButton: ElevatedButton(
     onPressed: () async {
       var height = await controller.runJavaScriptReturningResult(
           'document.scrollingElement.scrollHeight') as int;
       var scrollTo = height ~/ 2;
       controller.scrollTo(0, scrollTo);
     },
     child: Text('滚动一半内容'),
   )));

当然了,可能无滚动真正滚动一半内容,这个示例只是为了说明用 controller 的方法不能完成滚动任务时,可以用万能的 js。

WebView 的导航

  • canGoForward
  • canGoBack
  • goForward
  • goBack

前两个判断是否能前进与后退,后两个执行前进与后退。还是比较简单的。这四个都是 controller 上的方法,看名字就知道含意了。

页面来源

通过 url 获取页面

最简单的情况,只提供 URI 就行了。

var controller = WebViewController();
loadRequest(Uri.parse('https://www.csdn.net/');

可能你还想加上自定义 header,用来提供额外的信息。header 的格式:

{
   "key1":"value1",
   "key2":"value2",
}

key 要如何写呢?最初,推荐自定义headers 以 X- 开头,但是这种用法被 IETF 在 2012 年 6 月发布的 RFC 6648 明确弃用,原因是其会在非标准字段成为标准时造成不便。所以对于自定义 header 的key,直接命名就行。key 不区分大小写。 如果是多个词,中间用连字符隔开。

controller.loadRequest(Uri.parse('https://www.csdn.net/'),headers: {
     "Custom-Name":"IAM17"
});

如果内容过多,需要通过 body 发送。但一看 body 的参数类型傻眼了,是 Uint8List,这是什么鬼?

Uint8List 是 8 位无符号整数的固定长度列表。对于长列表,此实现比默认的 List 实现更节省空间和时间。我们理解成用 Uint8List 更高效就行了。那么如何把 String 转成 Uint8List 呢?

分两步

  1. 把 String 转成 List<int>
  2. List<int> 转成 unit8List
import 'dart:typed_data';
Uint8List toUint8List(String str) {

  final List<int> codeUnits = str.codeUnits;
  final unit8List = Uint8List.fromList(codeUnits);
  
  return unit8List;
}

我们综合起来,把参数都用上。注意:当 body 不为空的时候,method 必须为 LoadRequestMethod.post。

controller.loadRequest(Uri.parse('https://www.csdn.net/'),
    headers: {
      "Custom-Name": "IAM17",
    },
    method: LoadRequestMethod.post,
    body: toUint8List("我们见过的!"));

如果想要测试,可以把域名换成你自己的域名。在服务端就可以收到 header “Custom-Name”: “IAM17” 和 body “我们见过的!”

直接把 String 作为页面内容

 controller.loadHtmlString('<a href="/frontend">打开前端页面</a>', 
 baseUrl: 'https://www.csdn.net');

用 baseUrl 参数,html 中的链接就可以只写 path 的部分。

示例中的代码虽然也能运行,但还是建议要写完整的 html。

html 内容存在文件中

import 'dart:io';

var documentDirectory = await getApplicationDocumentsDirectory();
var filePath = '${documentDirectory.path}/index.html';
var indexFile = File(filePath);
await indexFile.writeAsString('IAM17');

controller.loadFile(filePath);

前面四句是在准备文件,示例只写入了 ‘IAM17’,实际上应该写入完整的 html 内容。运行示例能看到字(可能很小),说明运行成功。

html 内容在 Asset 中

实际上 Asset 这种方式也是把内容保存在文件中, 与 loadFile 不同的是 ,Asset 文件是随代码一起打包发布的。

我们来演练一下。

准备 Asset 文件。

在项目根目录下建立文件夹 html,在 html 下建立 index.html 文件,文件里放如下内容

<!DOCTYPE html>
<head>
<title>webview asset demo | IAM17</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, 
  maximum-scale=1.0, user-scalable=no,viewport-fit=cover" />
<style>
body{
   background:#BBDFFC;  
   text-align:center;
   color:#C45F84;
   padding-top:20px;
   font-size:22px;
}
</style>
</head>
<html>
<body>
大家好,我是 17
</body>
</html>

注册 Asset

打开 pubspec.yaml 增加下面的内容

 assets:
    - html/

运行代码

 controller.loadFlutterAsset('html/index.html');

会看到下面的效果

使用 NavigationDelegate

NavigationDelegate 可以拦截页面请求,以及跟踪导航请求的进度。

拦截

虽然前面的文章已经讲过拦截了,但因为这个内容比较重要,也是为了内容完整,这里再说一下。

 controller
      ..setNavigationDelegate(NavigationDelegate(
        onNavigationRequest: (request) {
          if (request.url.endsWith('/a.html')) {
         
             scheduleMicrotask(() =>
                controller.loadRequest(Uri.parse('http://localhost:8080/b.html'));
             
            return NavigationDecision.prevent;
          }
          return NavigationDecision.navigate;
        },
      ))
      ..loadRequest(Uri.parse('http://localhost:8080'));

逻辑上还是很简单的,根据 url 判断是否需要拦截。对需要拦截的 url 阻止本次请求。可以发起新的请求,也可以什么都不做。

页面加载完成

页面加载完成后,可以在这里运行 js,比如可以在这里拿到 cookie。

NavigationDelegate(
    onPageFinished: (url) async {
      var cookie = await controller
          .runJavaScriptReturningResult('document.cookie') as String;
      print(cookie);
})

如果你不需要拿到 js 的运行结果,可以用 controller.runJavaScript

有的页面资源很多,加载需要时间,可以用 NavigationDelegate 下面的 onProgress 回调做页面加载动画。

事件处理

在 ListView 中响应手势

默认情况下,WebViewWidget 在 listView 中是无法响应上下滑动的手势的。为了让 WebViewWidget 可以滑动查看 WebView 中的更多内容,需要指定 gestureRecognizers 属性。

ListView(
     children: [
       SizedBox(
         height: 600,
         child: WebViewWidget(gestureRecognizers: {
           Factory<VerticalDragGestureRecognizer>(() {
             return VerticalDragGestureRecognizer()
               ..onStart = (DragStartDetails details) {
                 print("start");
               }
               ..onDown = (DragDownDetails details) {
                 print("down: $details");
               };
           })
         }, controller: controller),
       )
     ],
);

看下 gestureRecognizers 属性的类型 Set<Factory<OneSequenceGestureRecognizer>> 可能会吓到一些小伙伴,我们一层一层拆解,其实也是很简单的。

最外层一个是 Set,是为了容纳多个手势,每种类型的手势只能有一个。

这里的 Factory 不同于构造函数那块使用的 factory(注意 f 是小写的)。构造函数前面的 factory 是一个关键字,这里的 Factory 是一个类。

class Factory<T> {
 
  const Factory(this.constructor) : assert(constructor != null);
  
  final ValueGetter<T> constructor;

  Type get type => T;

  
  String toString() {
    return 'Factory(type: $type)';
  }
}

关键在于 constructor,constructor 是 ValueGetter<T> 类型,ValueGetter<T>T Function() 的函数签名。回到 Factory 的构造函数,Factory 需要一个参数,参数的类型是 T Function()。这样就很明了了。现在 T 是 OneSequenceGestureRecognizer ,new 一个 Factory 类的实例,可以这样写:

Factory<VerticalDragGestureRecognizer>(
     () => VerticalDragGestureRecognizer());

VerticalDragGestureRecognizer 是 OneSequenceGestureRecognizer 的子类。

当然了,这样写也是一样的。

Factory<VerticalDragGestureRecognizer>(() {
    return VerticalDragGestureRecognizer();
});

把它放到 Set 里,就成了!

{
   Factory<VerticalDragGestureRecognizer>(() {
     return VerticalDragGestureRecognizer();
   })
}

可以对 VerticalDragGestureRecognizer 进行一些初始化化的操作。

{
    Factory<VerticalDragGestureRecognizer>(() {
      var recognizer = VerticalDragGestureRecognizer();
      recognizer.onStart = (DragStartDetails details) {
        print("start");
      };
      recognizer.onDown = (DragDownDetails details) {
        print("down: $details");
      };
      return recognizer;
    })
  }

级联 ( … ) 允许您对同一对象进行一系列操作。除了访问实例成员之外,您还可以在同一个对象上调用实例方法。这通常可以节省您创建临时变量的步骤,并允许您编写更流畅的代码。

本例中我们对 recognizer 连续进行了属性赋值,可以用级联简化代码,省掉中间变量 recognizer。

 {
    Factory<VerticalDragGestureRecognizer>(() {
       return VerticalDragGestureRecognizer()
         ..onStart = (DragStartDetails details) {
           print("start");
         }
         ..onDown = (DragDownDetails details) {
           print("down: $details");
         };
     })
   }

在其它滚动 widget 中也可以同样的方式让 webview 响应手势。

当有 widget 遮盖住 VebView,如何让事件穿透

这部分是附加的,跳过不会影响 WebView 的使用。

有时候,需要在 WebView 上浮动一些自己的 widget。在 Stack 中把这些 widget 放在 WebView 的后面就可以了,效果上 这些 widget 覆盖在 WebView 的上面。

在下面的示例中是一个半透明的绿色方块。点击这个绿色方块的时候,事件是不能穿透到 WebView 上面的。现在的需求是想让事件穿透到 WebView 上。

MaterialApp(
        home: Scaffold(
      body: SafeArea(
          child: Stack(
        children: [
          WebViewWidget(controller: controller),
          Positioned(
            top: 100,
            left: 0,
            right:0,
            child: Container(color: Color.fromRGBO(0, 250, 0, .3), height: 300,)),
        ],
      )

实现在的方案也是很简单的,用 IgnorePointer 把 container 包起来就行了。

 Stack(
       children: [
         WebViewWidget(controller: controller),
         Positioned(
             top: 100,
             left: 0,
             right: 0,
             child: IgnorePointer(
                 child: Container(
               color: Color.fromRGBO(0, 250, 0, .3),
               height: 300,
             ))),
       ],
     )

原理解析

简单来说,一个 Widget 要响应事件,需要先通过 HitTest。一个 Widget 有多个 child 的时候,只要有一个 child 通过点击测试,就可以响应事件。从后向前判断每一个 child 是否通过 HitTest, 后面的 child 测试通过,就不会再判断前面的 chid。

没有 IgnorePointer 的时候,Container HitTest 通过,WebViewWidget 没机会参与测试,当然也就没机会响应事件了。

加上 IgnorePointer 导致 Container HitTest 不通过,接着判断前面的 child WebViewWidget,WebViewWidget HitTest 通过,所以可以响应事件了。

关于事件的更多内容请参见

  • 【交互 widget】Flutter Listener
  • 【交互 widget】IgnorePointer 与 AbsorbPointer

到这里,WebView 的文章就全部结束了。 WebView 方面如果还有哪里没提到的,欢迎小伙伴们提出来,17 再补充。

番外

上周发了 Flutter WebView 如何与 h5 同步登录状态 后,收到系统通知,上榜了

CSDN每天值得看–2023-02-21

收到这个通知心中无比喜悦,自己的文章得到了官方认可!一个多月来写文的疲惫感一扫而空。

发完三篇 WebView 的文章后,总感觉有什么东西没提到。一想到下次再写 WebView 不知是何年月,我决定立即补上这篇新手入门的文章。

整花了一个月的时间,WebView 的文章终于齐整了。 写文花费的精力很大,自从节后复工以来,17 周末都没出过门,一心写文。17 自己都奇怪哪来这么大的劲头?原因是 小伙伴们的支持。只要小伙伴们支持我,17 就有动力写下去。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/374145.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI绘画进军三次元,有人用它打造赛博女友?(diffusion)

目录0 写在前面1 AI绘画技术飞跃2 效果展示3 环境配置3.1 下载基础模型3.2 更新.NET和模型3.3 下载绘画模型3.4 启动项目3.5 标签配置4 结语0 写在前面 机器学习强基计划聚焦深度和广度&#xff0c;加深对机器学习模型的理解与应用。“深”在详细推导算法模型背后的数学原理&a…

内存数据库-4-[redis]在ubuntu中离线安装

Ubuntu20.04(linux)离线安装redis 官网redis下载地址 下载安装包redis-6.0.9.tar.gz。 1 下载安装 (1)解压 sudo tar -xzvf redis-6.0.9.tar.gz -C /usr/local/ cd /usr/local/redis-6.0.9/(2)编译 sudo make(3)测试 sudo dpkg -i libtcl8.6_8.6.10dfsg-1_amd64.deb sudo d…

纯x86汇编实现的多线程操作系统实践 - 第七章 AP2的用户进程

AP2用户进程的代码为task2.asm。该用户进程将在界面上显示一个移动的弹球。一旦在界面上点击鼠标左键&#xff0c;弹球就会直接从鼠标点击处重新出现并继续移动。如何在界面上显示出一个持续移动的小球&#xff1f;计算小球将移动到的区域1->保留该区域中将被小球覆盖的点-&…

智慧物联网源码带手机端源码 物联网系统源码

在智慧工厂领域&#xff0c;智慧城市领域&#xff0c;都需要对设备进行监控。比如工厂需要对周围环境温度、湿度、气压、电压&#xff0c;灯的开关进行监控。这时候就需要物联网平台来进行管理。 推荐一个基于java开发的物联网平台&#xff0c;前端HTML带云组态、可接入视频监…

【华为OD机试模拟题】用 C++ 实现 - 网上商城优惠活动(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明网上商城优惠活动题目输入输出备注示例一输入输出说明输入说明输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才…

Android zygote进程启动流程

zygote启动过程中涉及到以下模块&#xff1a; app_processzygote USAPsocketFileDescriptor (FD) AndroidRuntimeAppRuntime &#xff08;定义于app_process模块&#xff0c;继承自AndroidRuntime。&#xff09; init进程启动zygote进程&#xff1a; #init.zygote32_64.rc s…

【华为OD机试模拟题】用 C++ 实现 - 统计匹配的二元组个数(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明统计匹配的二元组个数题目输入输出描述示例一输入输出说明示例二输入输出说明备注Code使用说明 参加华为od机试,一定要注意不要完全背诵代码&

python读写hdfs文件的实用解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…

Python学习笔记之环境搭建

Python学习笔记之环境搭建1. 下载Python2. Windows 安装最新Python3. Linux 安装最新PythonPython是一种编程语言&#xff0c;可以让您更快地工作并更有效地集成系统。 您可以学习使用Python&#xff0c;并立即看到生产力的提高和维护成本的降低。 Python是荷兰程序员吉多范罗苏…

使用 OpenCV 进行面部和眼睛检测

OpenCV是构建计算机视觉应用程序的强大工具。计算机视觉中最常见的任务之一是人脸检测&#xff0c;它涉及识别图像或视频中人脸的存在、位置和面部特征。在本文中&#xff0c;我们将学习如何使用 Haar 级联分类器检测图像中的人脸。先决条件在开始之前&#xff0c;你需要在计算…

Android打造万能的BannerView无限轮播图

效果图&#xff1a;工程目录图&#xff1a;BannerAdapter&#xff1a;banner轮播图的适配器&#xff0c;因为服务器返回的列表图片的url&#xff0c;显示的时候需要转成IamgeViw&#xff1b; BannerScroller&#xff1a;设置切换页面的持续时间&#xff1b; BannerView&…

怎样划分MES系统实施阶段,三分钟告诉你整体实施思路

MES系统的实施阶段划分的思路是&#xff1a;在集成的前提下实现可视化&#xff0c;在可视化的基础上实现精细化&#xff0c;在精细化的前提下实现均衡化。生产过程透明的目的就是要实现生产过程的可视化&#xff0c;实现精细化生产。首先要做的就是收集生产信息&#xff0c;这是…

Linux->进程地址空间

目录 前言&#xff1a; 1. 程序地址空间回顾 2. 进程空间是什么 3. 进程地址空间与内存 4. 进程地址空间和内存的关联 5. 为什么要有进程地址空间 前言&#xff1a; 我们在平时学习的过程当中总是听到栈、堆、代码段等等储存空间&#xff0c;但是这些东西到底是什么&…

CSS3新特性

CSS3新特性 1.1、字体图标 何为字体图标&#xff1f; 字体图标展示的是图标&#xff0c;本质是字体。用于处理简单的、颜色单一的图片 字体图标的优点&#xff1a; 灵活性&#xff1a;灵活地修改样式&#xff0c;例如&#xff1a;尺寸、颜色等轻量级&#xff1a;体积小、渲…

Java并发编程(1)—— 操作系统、Linux、Java中进程与线程的区别

一、操作系统中什么是线程和进程 线程和进程都是操作系统中定义的结构&#xff0c;进程是系统中一个独立的活动程序&#xff0c;比如像QQ、网易云音乐&#xff0c;进程是操作系统进行资源分配的基本单位&#xff0c;一个进程中的所有线程共享进程内的资源&#xff0c;而线程则…

【Python学习笔记】第十八节 Python 内置函数

Python 内置函数内置函数就是Python给你提供的, 拿来直接用的函数&#xff0c;比如print&#xff0c;input等Python 内置函数一览表内置函数abs()divmod()input()open()staticmethod()all()enumerate()int()ord()str()any()eval()isinstance()pow()sum()basestring()execfile()…

ARMv8 同步和信号量(读写一致性问题):Load-Exclusive/Store-Exclusive指令详解

目录 一&#xff0c;Local Monitor 与 Global Monitor 1&#xff0c;Local Monitor 2&#xff0c;Global Monitor 二&#xff0c;Exclusive 指令的简单使用 三&#xff0c;Exclusive 示例程序 1&#xff0c;原子自加1程序 2&#xff0c;原子锁程序 四&#xff0c; 多处理…

算法设计与智能计算 || 专题一: 算法基础

专题一: 算法基础 文章目录专题一: 算法基础1. 算法的定义及特点1.1 算法的基本特征1.2 算法的基本要素1.3 算法的评定2 算法常见执行方法2.1 判断语句2.2 循环语句2.3 综合运用3. 计算复杂度4. 代码的重用5. 类函数的定义与使用5.1 定义类5.2 调用类函数1. 算法的定义及特点 …

_hand-2

实现一个迷你版的vue 入口 // js/vue.js class Vue {constructor (options) {// 1. 通过属性保存选项的数据this.$options options || {}this.$data options.data || {}this.$el typeof options.el string ? document.querySelector(options.el) : options.el// 2. 把da…

php mysql高校田径运动会成绩管理系统

第一章 引言 1 1.1 选题背景 1 1.2 编写目的 2 1.3 目标 2 1.4 功能需求 3 第二章 开发工具介绍 4 2.1 PHP 4 2.2 APACHE 5 2.3 MYSQL数据库 5 2.4 运行环境 WINDOWS XP 6 2.5 XAMPP 6 2.6 DREAMWEAVE8 6 2.7 EDITPLUS 7 第三章 需求…