我们的应用程序现在可以点击查看图像,但还没有实现查看时放大、缩小与移动图像。要实现这个功能,需要监听用户在图像上的操作,并调用相应的回调处理用户操作。我们先将Transform控件从_ImageZoomableStatebuild方法中拆分出来。在_ImageZoomableState类中添加_drawImage方法。

class _ImageZoomableState extends State<ImageZoomable> {//...Widget _drawImage() {if (_image == null) {return null;}return new Transform(transform: new Matrix4.diagonal3Values(1.0, 1.0, 1.0),child: new CustomPaint(painter: new _ImageZoomablePainter(image: _image,offset: Offset.zero,zoom: 1.0,)));}//...
}

修改_ImageZoomableStatebuild方法,使用GestureDetector控件包装_drawImage方法中的Transform控件,我们可以使用GestureDetector控件监听用户的各种操作手势。

class _ImageZoomableState extends State<ImageZoomable> {//...@overrideWidget build(BuildContext context) {return new GestureDetector(child: _drawImage(),);}//...
}

图片缩放需要一个比例,在ImageZoomable类定义中声明一个成员变量scale来设置图像的显示比例,同时为变量scale设置默认值,使scale变量为可选参数。

class ImageZoomable extends StatefulWidget {ImageZoomable(this.image, {Key key, this.scale = 2.0}) : super(key: key);final ImageProvider image;final double scale;@override_ImageZoomableState createState() => new _ImageZoomableState(scale);
}

_ImageZoomableState类定义中接收ImageZoomable类成员变量scale,并声明三个Offset(不可变的二维浮点偏移量)类型的成员变量。_startingFocalPoint变量存储初始焦点值,_previousOffset变量存储历史偏移量,_offset变量使用静态方法Offset.zero为其赋值,Offset类的静态方法zero返回一个零幅度偏移量,即常量Offset(0.0, 0.0)_zoom_previousZoom则是用于存储缩放值与存储历史缩放值。

class _ImageZoomableState extends State<ImageZoomable> {_ImageZoomableState(this._scale);final double _scale;ImageStream _imageStream;ui.Image _image;Offset _startingFocalPoint;Offset _previousOffset;Offset _offset = Offset.zero;double _zoom = 1.0;double _previousZoom;//...
}

现在我们可以修改_drawImage方法中Transform控件的硬编码。

class _ImageZoomableState extends State<ImageZoomable> {
//...Widget _drawImage() {//...return new Transform(transform: new Matrix4.diagonal3Values(_scale, _scale, _scale),child: new CustomPaint(painter: new _ImageZoomablePainter(image: _image,offset: _offset,zoom: _zoom / _scale,)));}//...
}

现在回到main.dart文件中来,修改ImageZoomable类的调用。

class ChatMessage extends StatelessWidget {//...@overrideWidget build(BuildContext context) {return new SizeTransition(//...onTap: (){Navigator.of(context).push( new MaterialPageRoute<Null>(builder: (BuildContext context) {return new ImageZoomable(new NetworkImage(snapshot.value['imageUrl']));}));},//...);}
}

我们要在用户对图像操作之前获取图像偏移位置相关的参数,也就是之前声明一些_ImageZoomableState类成员变量。我们需要在_ImageZoomableState类中增加_handleScaleStart方法作为手势监听器onScaleStart的回调函数。

手势监听器onScaleStart触发回调的条件:与屏幕接触的指针已经建立起了一个焦点时,此时初始图像显示比例为1.0。参数details的类型为ScaleStartDetails,该类型存储手势监听器onScaleStart的详细信息,其focalPoint属性以全局坐标方式返回与屏幕接触的指针初始焦点。

class _ImageZoomableState extends State<ImageZoomable> {//...void _handleScaleStart(ScaleStartDetails details) {if (_image == null) {return;}_startingFocalPoint = details.focalPoint / _scale;_previousOffset = _offset;_previousZoom = _zoom;}//...
}

现在我们在_ImageZoomableState类的GestureDetector控件中添加一个onScaleStart监听器,把上面的_handleScaleStart方法作为回调。

class _ImageZoomableState extends State<ImageZoomable> {//...@overrideWidget build(BuildContext context) {return new GestureDetector(child: _drawImage(),onScaleStart: _handleScaleStart,);}//...
}

在用户对图像操作时,我们需要跟踪焦点的移动,并重新绘制图像。因此我们需要在_ImageZoomableState类中增加_handleScaleUpdate方法作为手势监听器onScaleUpdate的回调函数,手势监听器onScaleUpdate会监听焦点的变化,并在焦点变化时调用回调处理。

_handleScaleUpdate方法的参数details的类型为ScaleUpdateDetails,该类型存储手势监听器onScaleUpdate的详细信息,其focalPoint属性以全局坐标方式返回与屏幕接触的指针焦点。

void _handleScaleUpdate(Size size, ScaleUpdateDetails details) {if (_image == null) {return;}double newZoom = _previousZoom * details.scale;bool tooZoomedIn = _image.width * _scale / newZoom <= size.width ||_image.height * _scale / newZoom <= size.height || newZoom <= 0.8;if (tooZoomedIn) {return;}setState(() {_zoom = newZoom;final Offset normalizedOffset = (_startingFocalPoint - _previousOffset) / _previousZoom;_offset = details.focalPoint / _scale - normalizedOffset * _zoom;});}

上面定义了两个局部变量,newZoom存储最新缩放值,tooZoomedIn变量判断图像是否过于放大或缩小。如果tooZoomedIn变量为真时,则不会重新绘制图像。局部变量normalizedOffset用于确保焦点下方的图像保持在相同位置的前提下放大图像。

现在我们在_ImageZoomableState类的GestureDetector控件中添加一个onScaleUpdate监听器,把上面的_handleScaleUpdate方法作为回调。

class _ImageZoomableState extends State<ImageZoomable> {//...@overrideWidget build(BuildContext context) {return new GestureDetector(child: _drawImage(),onScaleStart: _handleScaleStart,onScaleUpdate: (d) => _handleScaleUpdate(context.size, d),);}//...
}

目前用户在查看图像时,需要点击系统的后退按钮才能返回到聊天屏幕。在大部分应用程序中,在查看图像时点击即可返回聊天屏幕,我们也可以这么做。在ImageZoomable类定义中声明一个GestureTapCallback类型的成员变量onTapGestureTapCallback类型表示点击发生时的回调函数。

class ImageZoomable extends StatefulWidget {ImageZoomable(this.image, {Key key, this.scale = 2.0, this.onTap}) : super(key: key);final ImageProvider image;final double scale;final GestureTapCallback onTap;@override_ImageZoomableState createState() => new _ImageZoomableState(scale);
}

然后我们需要在_ImageZoomableState类的GestureDetector控件中添加一个onTap监听器,并把ImageZoomable类的成员变量onTap作为回调。

class _ImageZoomableState extends State<ImageZoomable> {//...@overrideWidget build(BuildContext context) {return new GestureDetector(child: _drawImage(),onTap: widget.onTap,onScaleStart: _handleScaleStart,onScaleUpdate: (d) => _handleScaleUpdate(context.size, d),);}//...
}

最后回到main.dart文件中来,修改ImageZoomable类的调用。

class ChatMessage extends StatelessWidget {//...@overrideWidget build(BuildContext context) {return new SizeTransition(//...onTap: (){Navigator.of(context).push( new MaterialPageRoute<Null>(builder: (BuildContext context) {return new ImageZoomable(new NetworkImage(snapshot.value['imageUrl']),onTap: (){Navigator.of(context).pop();},);}));},//...);}
}

现在图像查看,且查看时可以缩放、移动图像的功能已经完成了!

Flutter实战一Flutter聊天应用(十一)相关推荐

  1. Flutter实战一Flutter聊天应用(汇总)

    纸聊 这个应用程序使用Google的Flutter移动框架开发,是一个实时聊天应用程序,为了能专注于APP设计,应用程序的服务端使用Googler的Firebase平台.程序程序的名称为纸聊,意为像传 ...

  2. Flutter实战一Flutter聊天应用(二十)

    在上一篇文章<Flutter实战一Flutter聊天应用(十九)>中,我们完成了删除用户的逻辑,就是将会话的有效性设置为false就可以了.那么当会话的有效性为false时,用户再次添加该 ...

  3. Flutter实战一Flutter聊天应用(十六)

    在上一篇文章<Flutter实战一Flutter聊天应用(十五)>中,我们完成了登陆屏幕.在用户登陆成功后,会在本地创建一个LandingInformation文件,以使应用程序在启动时可 ...

  4. Flutter实战一Flutter聊天应用(十五)

    在上一篇文章<Flutter实战一Flutter聊天应用(十四)>中,我们完成了注册屏幕.为了保持应用程序入口只有一个,即登陆屏幕,用户注册完成之后会返回手机号码.密码到登陆屏幕,让用户点 ...

  5. Flutter实战一Flutter聊天应用(五)

    我们的应用程序现在已经有了一个好看的UI,但是我们还没有一个后端.所以我们要买一个云服务器,然后再安装数据库?当然不是!我们可以使用Firebase平台作为后端,那么Firebase是什么呢? Fir ...

  6. Flutter实战一Flutter聊天应用(二十一)

    在这一系列的前二十篇文章里,我们已经完成了最主要的添加.删除好友,并与好友聊天,还可以发送图片的功能.这一篇文章会完成个人资料与设置相关的功能,并将应用发布上线. 之前设置了个人资料的入口按钮,现在我 ...

  7. Flutter实战一Flutter聊天应用(十九)

    在上一篇文章中,我们完成了聊天列表的用户界面与功能代码.在用户添加完会话后,聊天列表会增加对应的会话项,通过点击会话项,可以进入聊天屏幕.在这一篇文章中,我们主要是修改lib/chat_screen. ...

  8. Flutter实战一Flutter聊天应用(十八)

    在上一篇文章中,我们完成了基本的添加聊天功能,但是还没有在聊天列表显示添加的新聊天,在这篇文章中我们将实现这个功能--在聊天列表中展示所有的聊天. 首先,我们在/lib目录下新建一个group_cha ...

  9. Flutter实战一Flutter聊天应用(十)

    首先,我们要修复一下之前几篇文章中存在的缺陷.在发送超过两行的消息时,屏幕上显示的消息不会自动换行,会超出最大宽度.我们可以通过将Text包装在Container控件中,再添加一个width属性,使其 ...

最新文章

  1. 华为选拔人才的五个素质
  2. android模糊查询listview数据_ListView的简单应用(一)
  3. Docker进阶-快速扩容
  4. “睡服”面试官系列第十七篇之Reflect(建议收藏学习)
  5. 软件测试nodejs面试题,nodejs单元测试和性能测试
  6. selenium + python自动化测试unittest框架学习(一)selenium原理及应用
  7. wordpress 运行_如何为您的教室设置和运行WordPress
  8. 有关Activity的Launch mode 以及Intent的setFlags(转载)
  9. 项目的ip地址更改,用git从远程提取代码出现错误,提示为 network error connection timed out...
  10. 三坐标测量圆直径_多台三坐标测量机联动测量方法的研究
  11. 计算机辅助设计工业产品cad竞赛试题,2017工业产品设计CAD比赛试题
  12. 在docker中配置apt工具与python的源均为国内源
  13. vue路由(router)设置:父路由默认选中第一个子路由,切换子路由让父路由高亮不会消失
  14. LED的基本操作(138译码器 573锁存器)
  15. 安卓一键新机_「科技犬」三星华为频发新品对标iPhone12,稳固安卓生态
  16. 报告丨前瞻产业研究院:2019年中国大数据行业研究报告
  17. windows 配置中科大的 Rust 下载云,提高下载速度
  18. R: ggplot2图片的布局排版
  19. 【C++课程设计项目】歌手评分系统(代码量1500行含设计文档)
  20. 应广单片机adc_台湾应广单片机 单片机PMC131 带12位ADC、采用FPPATM技术

热门文章

  1. 豆瓣电影 知识图谱 Neo4j
  2. LeetCode 1116. 打印零与奇偶数
  3. ~~Kruskal算法
  4. 《南溪的目标检测学习笔记》——COCO数据集的学习笔记
  5. 突击计划——给定三角形边长,求面积
  6. Altium AD20更改原理图的连接节点颜色和连线颜色(结点颜色)
  7. 字符串匹配问题 ----- Rabin-Karp算法
  8. CleanCodeHandbook Chapter 9: Binary Search(48-50)
  9. 【GTK3.0】背景设置
  10. Play静态文件调用