扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
Flutter的webview常用的第三方库有 flutter_webview_plugin 、 webview_flutter ,后者的文档较少,暂先学习flutter_webview_plugin。
10余年建站经验, 成都做网站、网站设计客户的见证与正确选择。创新互联建站提供完善的营销型网页建站明细报价表。后期开发更加便捷高效,我们致力于追求更美、更快、更规范。
添加依赖
导入包
iOS端info.plist配置,其中NSAppTransportSecurity节点是为了支持http协议
一个简单的demo
要监听链接跳转的话,实现onUrlChanged即可
添加依赖
导入包
iOS端info.plist配置
一个简单的demo
但是在webview里点击链接跳转的时候,测试机有时会跳转到系统浏览器上,并且点击文本框无法弹出键盘,交互性很弱。
二者共同的缺点是与javascript难以交互,目前只能实现Flutter-JS传递信息,还没找到可以进行完美交互的第三方库。并且一些常见的协议还不支持,比如拨号和调用摄像头等,期待后续完善。
Dart的 IO 库包含了文件读写的相关类,它属于 Dart 语法标准的一部分,所以通过 Dart IO 库,无论是 Dart VM 下的脚本还是 Flutter,都是通过 Dart IO 库来操作文件的,不过和 Dart VM 相比,Flutter 有一个重要差异是文件系统路径不同,这是因为Dart VM 是运行在 PC 或服务器操作系统下,而 Flutter 是运行在移动操作系统中,他们的文件系统会有一些差异。
Android 和 iOS 的应用存储目录不同, PathProvider 插件提供了一种平台透明的方式来访问设备文件系统上的常用位置。该类当前支持访问两个文件系统位置:
File代表一个整体的文件,他有三个构造函数,分别是:
文件读取本身有两种形式,一种是文本,一种是二进制。
2.2.1 读取文本内容
如果是文本文件,File提供了readAsString、readAsLines、readAsStringSync、readAsLinesSync方法,读取文本内容
readAsString 一次性读取所有文本
readAsLines 一行行的读取文本
结果返回的是一个List,list中表示文件每行的内容
readAsStringSync、readAsLinesSync同步读取文本
2.2.2 读取二进制内容
如果文件是二进制,那么可以使用readAsBytes或者同步的方法readAsBytesSync:
dart中表示二进制有一个专门的类型叫做Uint8List,他实际上表示的是一个int的List。
上面提到的读取方式,都是一次性读取整个文件,缺点就是如果文件太大的话,可能造成内存空间的压力。
所以File为我们提供了另外一种读取文件的方法,流的形式来读取文件.
示例
dart提供了open和openSync两个方法来进行随机文件读写:
写入和文件读取一样,可以一次性写入或者获得一个写入句柄,然后再写入。
一次性写入的方法有四种,分别对应字符串和二进制
句柄形式可以调用openWrite方法,返回一个IOSink对象,然后通过这个对象进行写入:
默认情况下写入是会覆盖整个文件的,但是可以通过下面的方式来更改写入模式:
虽然dart中所有的异常都是运行时异常,但是和java一样,要想手动处理文件读写中的异常,则可以使用try,catch:
我们还是以计数器为例,实现在应用退出重启后可以恢复点击次数。 这里,我们使用文件来保存数据:
1.引入PathProvider插件;在pubspec.yaml文件中添加如下声明:
执行 flutter pub get
2.实现如下
参考:
Flutter 中有两种布局模型:
基于 RenderBox 的盒模型布局。
基于 Sliver ( RenderSliver ) 按需加载列表布局。
通常可滚动组件的子组件可能会非常多、占用的总高度也会非常大;如果要一次性将子组件全部构建出将会非常昂贵!为此,Flutter中提出一个Sliver(中文为“薄片”的意思)概念,Sliver 可以包含一个或多个子组件。Sliver 的主要作用是配合:加载子组件并确定每一个子组件的布局和绘制信息,如果 Sliver 可以包含多个子组件时,通常会实现按需加载模型。
只有当 Sliver 出现在视口中时才会去构建它,这种模型也称为“基于Sliver的列表按需加载模型”。可滚动组件中有很多都支持基于Sliver的按需加载模型,如 ListView 、 GridView ,但是也有不支持该模型的,如 SingleChildScrollView 。
Flutter 中的可滚动主要由三个角色组成: Scrollable 、 Viewport 和 Sliver :
具体布局过程:
比如有一个 ListView,大小撑满屏幕,假设它有 100 个列表项(都是RenderBox)且每个列表项高度相同,结构如图6-1所示:
图中白色区域为设备屏幕,也是 Scrollable 、 Viewport 和 Sliver 所占用的空间,三者所占用的空间重合,父子关系为:Sliver 父组件为 Viewport,Viewport的 父组件为 Scrollable 。注意ListView 中只有一个 Sliver,在 Sliver 中实现了子组件的按需加载。
其中顶部和底部灰色的区域为 cacheExtent,它表示预渲染的高度,需要注意这是在可视区域之外,如果 RenderBox 进入这个区域内,即使它还未显示在屏幕上,也是要先进行构建的,预渲染是为了后面进入 Viewport 的时候更丝滑。cacheExtent 的默认值是 250,在构建可滚动列表时我们可以指定这个值,这个值最终会传给 Viewport。
用于处理滑动手势,确定滑动偏移,滑动偏移变化时构建 Viewport,我们看一下其关键的属性:
在可滚动组件的坐标描述中,通常将滚动方向称为主轴,非滚动方向称为纵轴。由于可滚动组件的默认方向一般都是沿垂直方向,所以默认情况下主轴就是指垂直方向,水平方向同理。
Viewport 比较简单,用于渲染当前视口中需要显示 Sliver。
需要注意的是:
Sliver 主要作用是对子组件进行构建和布局,比如 ListView 的 Sliver 需要实现子组件(列表项)按需加载功能,只有当列表项进入预渲染区域时才会去对它进行构建和布局、渲染。
Sliver 对应的渲染对象类型是 RenderSliver,RenderSliver 和 RenderBox 的相同点是都继承自 RenderObject 类,不同点是在布局的时候约束信息不同。RenderBox 在布局时父组件传递给它的约束信息对应的是 BoxConstraints ,只包含最大宽高的约束;而 RenderSliver 在布局时父组件(列表)传递给它的约束是对应的是 SliverConstraints 。关于 Sliver 的布局协议,我们将在本章最后一节中介绍。
几乎所有的可滚动组件在构造时都能指定 scrollDirection (滑动的主轴)、 reverse (滑动方向是否反向)、 controller 、 physics 、 cacheExtent ,这些属性最终会透传给对应的 Scrollable 和 Viewport,这些属性我们可以认为是可滚动组件的通用属性,后续再介绍具体的可滚动组件时将不再赘述。
可滚动组件都有一个 controller 属性,通过该属性我们可以指定一个 ScrollController 来控制可滚动组件的滚动,比如可以通过ScrollController来同步多个组件的滑动联动。由于 ScrollController 是需要结合可滚动组件一起工作,所以本章中,我们会在介绍完 ListView 后详细介绍 ScrollController。
Scrollbar是一个Material风格的滚动指示器(滚动条),如果要给可滚动组件添加滚动条,只需将Scrollbar作为可滚动组件的任意一个父级组件即可,如:
Scrollbar 和 CupertinoScrollbar 都是通过监听滚动通知来确定滚动条位置的。关于的滚动通知的详细内容我们将在本章最后一节中专门介绍。
CupertinoScrollbar是 iOS 风格的滚动条,如果你使用的是Scrollbar,那么在iOS平台它会自动切换为CupertinoScrollbar
手势操作在 Flutter 中分为两类:
第一类是原始的指针事件(Pointer Event),即原生开发中常见的触摸事件,表示屏幕上触摸(或鼠标、手写笔)行为触发的位移行为;
第二类则是手势识别(Gesture Detector),表示多个原始指针事件的组合操作,如点击、双击、长按等,是指针事件的语义化封装。
指针事件表示用户交互的原始触摸数据,如手指接触屏幕 PointerDownEvent、手指在屏幕上移动 PointerMoveEvent、手指抬起 PointerUpEvent,以及触摸取消 PointerCancelEvent。在手指接触屏幕,触摸事件发起时,Flutter 会确定手指与屏幕发生接触的位置上究竟有哪些组件,并将触摸事件交给最内层的组件去响应。事件会从这个最内层的组件开始,沿着组件树向根节点向上冒泡分发。通过 hitTestBehavior 去调整组件在命中测试期内应该如何表现,比如把触摸事件交给子组件,或者交给其视图层级之下的组件去响应。关于组件层面的原始指针事件的监听,Flutter 提供了 Listener Widget,可以监听其子 Widget 的原始指针事件。
Listener(
child: Container(
color: Colors.black,
width: 300,
height: 300,
),
onPointerDown: (event) = print("down $event"),// 手势按下回调
onPointerMove: (event) = print("move $event"),// 手势移动回调
onPointerUp: (event) = print("up $event"),// 手势抬起回调
);
Gesture 是手势语义的抽象,而如果我们想从组件层监听手势,则需要使用 GestureDetector 。GestureDetector 是一个处理各种高级用户触摸行为的 Widget,与 Listener 一样,也是一个功能性组件。
GestureDetector(// 手势识别
child: Container(color: Colors.red,width: 50,height: 50),// 红色子视图
onTap: ()=print("Tap"),// 点击回调
onDoubleTap: ()=print("Double Tap"),// 双击回调
onLongPress: ()=print("Long Press"),// 长按回调
onPanUpdate: (e) {// 拖动回调
setState(() {
// 更新位置
_left += e.delta.dx;
_top += e.delta.dy;
});
},
),
Http协议是无状态的,只能由客户端主动发起,服务端再被动响应,服务端无法向客户端主动推送内容,并且一旦服务器响应结束,链接就会断开所以无法进行实时通信。WebSocket协议正是为解决客户端与服务端实时通信而产生的技术,现在已经被主流浏览器支持。目前 Flutter也提供了专门的包来支持WebSocket协议。
web_socket_channel package 提供了我们需要连接到WebSocket服务器的工具。该package提供了一个 WebSocketChannel 允许我们既可以监听来自服务器的消息,又可以将消息发送到服务器的方法。
执行flutter pub get 命令,即可
1. 连接到WebSocket服务器
2. 监听来自服务器的消息
使用一个 StreamBuilder 来监听新消息, 并用一个Text来显示它们
工作原理
WebSocketChannel 提供了一个来自服务器的消息 Stream 。该 Stream 类是 dart:async 包中的一个基础类。它提供了一种方法来监听来自数据源的异步事件。与 Future 返回单个异步响应不同, Stream 类可以随着时间推移传递很多事件。该 StreamBuilder 组件将连接到一个 Stream , 并在每次收到消息时通知Flutter重新构建界面
3. 将数据发送到服务器
为了将数据发送到服务器,我们会add消息给WebSocketChannel提供的sink。
WebSocketChannel 提供了一个 StreamSink ,它将消息发给服务器
StreamSink 类提供了给数据源同步或异步添加事件的一般方法
4. 关闭WebSocket连接
思考:
假如我们想通过WebSocket传输二进制数据应该怎么做(比如要从服务器接收一张图片)?我们发现StreamBuilder和Stream都没有指定接收类型的参数,并且在创建WebSocket链接时也没有相应的配置,貌似没有什么办法……其实很简单,要接收二进制数据仍然使用StreamBuilder,因为WebSocket中所有发送的数据使用帧的形式发送,而帧是有固定格式,每一个帧的数据类型都可以通过Opcode字段指定,它可以指定当前帧是文本类型还是二进制类型(还有其它类型),所以客户端在收到帧时就已经知道了其数据类型,所以flutter完全可以在收到数据后解析出正确的类型,所以就无需开发者去关心,当服务器传输的数据是指定为二进制时,StreamBuilder的snapshot.data的类型就是Listint,是文本时,则为String。
对动画系统而言,为了实现动画,它需要做三件事儿:1.确定画面变化的规律;2.根据这个规律,设定动画周期,启动动画;3.定期获取当前动画的值,不断地微调、重绘画面。
这三件事情对应到 Flutter 中,就是 Animation、AnimationController 与 Listener:
1.Animation 是 Flutter 动画库中的核心类,会根据预定规则,在单位时间内持续输出动画的当前状态。Animation 知道当前动画的状态(比如,动画是否开始、停止、前进或者后退,以及动画的当前值),但却不知道这些状态究竟应用在哪个组件对象上。换句话说,Animation 仅仅是用来提供动画数据,而不负责动画的渲染。
2.AnimationController 用于管理 Animation,可以用来设置动画的时长、启动动画、暂停动画、反转动画等。
3.Listener 是 Animation 的回调函数,用来监听动画的进度变化,我们需要在这个回调函数中,根据动画的当前值重新渲染组件,实现动画的渲染。
class NormalAnimateWidget extends StatefulWidget {
@override
StatecreateState()=_NormalAnimateState();
}
class _NormalAnimateState extends Statewith SingleTickerProviderStateMixin{
AnimationController?controller;
Animation?animation;
@override
void initState() {
// TODO: implement initState
super.initState();
/*
* AnimationController
AnimationController用于控制动画,它包含动画的启动forward()、停止stop() 、反向播放 reverse()等方法。
* AnimationController会在动画的每一帧,就会生成一个新的值。
* 默认情况下,AnimationController在给定的时间段内线性的生成从 0.0 到1.0(默认区间)的数字。
* */
/*Ticker
当创建一个AnimationController时,需要传递一个vsync参数,
它接收一个TickerProvider类型的对象,它的主要职责是创建Ticker,定义如下:
abstract class TickerProvider {
//通过一个回调创建一个Ticker
Ticker createTicker(TickerCallback onTick);
}
Flutter 应用在启动时都会绑定一个SchedulerBinding,
通过SchedulerBinding可以给每一次屏幕刷新添加回调,
而Ticker就是通过SchedulerBinding来添加屏幕刷新回调,这样一来,
每次屏幕刷新都会调用TickerCallback。
使用Ticker(而不是Timer)来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)
消耗不必要的资源,因为Flutter中屏幕刷新时会通知到绑定的SchedulerBinding,
而Ticker是受SchedulerBinding驱动的,
由于锁屏后屏幕会停止刷新,所以Ticker就不会再触发。
*/
// 创建动画周期为1秒的AnimationController对象
controller =AnimationController(
vsync:this, duration:const Duration(milliseconds:3000));
/*
* Curve
* 动画过程可以是匀速的、匀加速的或者先加速后减速等。
* Flutter中通过Curve(曲线)来描述动画过程,
* 我们把匀速动画称为线性的(Curves.linear),而非匀速动画称为非线性的。
* 我们可以通过CurvedAnimation来指定动画的曲线,如:
final CurvedAnimation curve =
CurvedAnimation(parent: controller, curve: Curves.easeIn);
*
Curves曲线 动画过程
linear 匀速的
decelerate 匀减速
ease 开始加速,后面减速
easeIn 开始慢,后面快
easeOut 开始快,后面慢
easeInOut 开始慢,然后加速,最后再减速
*
* 当然我们也可以创建自己Curve,例如我们定义一个正弦曲线:
class ShakeCurve extends Curve {
@override
double transform(double t) {
return math.sin(t * math.PI * 2);
}
}
* */
final CurvedAnimation curve =CurvedAnimation(
parent:controller!, curve:Curves.linear);
/*
* Animation
*Animation是一个抽象类,它本身和UI渲染没有任何关系,
* 而它主要的功能是保存动画的插值和状态;其中一个比较常用的Animation类是Animation。
* Animation对象是一个在一段时间内依次生成一个区间(Tween)之间值的类。
* Animation对象在整个动画执行过程中输出的值可以是线性的、曲线的、一个步进函数或者任何其他曲线函数等等,
* 这由Curve来决定。 根据Animation对象的控制方式,
* 动画可以正向运行(从起始状态开始,到终止状态结束),
* 也可以反向运行,甚至可以在中间切换方向。
* Animation还可以生成除double之外的其他类型值
* ,如:Animation 或Animation。
* 在动画的每一帧中,我们可以通过Animation对象的value属性获取动画的当前状态值。
#动画通知
我们可以通过Animation来监听动画每一帧以及执行状态的变化,Animation有如下两个方法:
addListener();它可以用于给Animation添加帧监听器,
* 在每一帧都会被调用。
* 帧监听器中最常见的行为是改变状态后调用setState()来触发UI重建。
addStatusListener();
* 它可以给Animation添加“动画状态改变”监听器;
* 动画开始、结束、正向或反向(见AnimationStatus定义)时会调用状态改变的监听器。
* */
// 创建从50到200线性变化的Animation对象
// 普通动画需要手动监听动画状态,刷新UI
animation =Tween(begin:10.0, end:200.0).animate(curve)
..addListener(()=setState((){}));
/*
* Tween
* 默认情况下,AnimationController对象值的范围是[0.0,1.0]。
* 如果我们需要构建UI的动画值在不同的范围或不同的数据类型,
* 则可以使用Tween来添加映射以生成不同的范围或数据类型的值。
*Tween构造函数需要begin和end两个参数。
* Tween的唯一职责就是定义从输入范围到输出范围的映射。
* 输入范围通常为[0.0,1.0],但这不是必须的,我们可以自定义需要的范围。
* */
// 启动动画
controller!.repeat(reverse:true);
//
// 第二段
// animation!.addStatusListener((status) {
// if (status == AnimationStatus.completed) {
// controller!.reverse();// 动画结束时反向执行
// } else if (status == AnimationStatus.dismissed) {
// controller!.forward();// 动画反向执行完毕时,重新执行
// }
// });
// controller!.forward();// 启动动画
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home:Scaffold(
body:Center(
child:Container(
width:animation!.value,// 将动画的值赋给 widget 的宽高
height:animation!.value,//
child:FlutterLogo(),
)
)
)
);
}
@override
void dispose() {
// 释放资源
controller!.dispose();
super.dispose();
}
}
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流