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

class ChatMessage extends StatelessWidget {//...@overrideWidget build(BuildContext context) {return new SizeTransition(//...new Container(margin: const EdgeInsets.only(top: 5.0),child: snapshot.value['imageUrl'] != null ?new Image.network(snapshot.value['imageUrl'],width: 250.0,):new Container(width: MediaQuery.of(context).size.width * 0.8,child: new Text(snapshot.value['text']),),//...);}
}

使用MediaQuery可以获取了解当前媒体的大小,我们可以从MediaQuery.of返回的MediaQueryData中读取MediaQueryData.size属性。比如上面代码中的MediaQuery.of(context).size.width可以获得当前屏幕的宽度。

现在应用程序不会因为消息太长而超出屏幕宽度,但这是通过硬编码的方式解决的。我们有更好的解决方案,将显示发送人姓名和消息的Column包装在Flexible控件中,使其填充Row主轴中的可用空间。

class ChatMessage extends StatelessWidget {//...@overrideWidget build(BuildContext context) {return new SizeTransition(//...child: new Container(margin: const EdgeInsets.symmetric(vertical: 10.0),child: new Row(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[new Container(//...),new Flexible(child: new Column(crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[new Text(snapshot.value['senderName'],style: Theme.of(context).textTheme.subhead),new Container(margin: const EdgeInsets.only(top: 5.0),child: snapshot.value['imageUrl'] != null ?new Image.network(snapshot.value['imageUrl'],width: 250.0,):new Text(snapshot.value['text']),)//...);}
}

Text控件的父级控件必须有一个相对或固定宽度才能自动换行,如果其父级没有设置宽度,则在父级的上一级中寻找宽度。

回到正题,如果用户想要点击查看原图,这是一个很常见的用户操作,因此我们将实现这个功能。首先,我们在项目的lib目录下创建一个image_zoomable.dart文件,并添加下面的代码。

import 'package:flutter/material.dart';class ImageZoomable extends StatefulWidget {ImageZoomable({Key key}) : super(key: key);@override_ImageZoomableState createState() => new _ImageZoomableState();
}class _ImageZoomableState extends State<ImageZoomable> {@overrideWidget build(BuildContext context) {return new Text('图片查看屏幕');}
}

现在回到main.dart文件中来,首先我们需要导入image_zoomable.dart文件。

import 'image_zoomable.dart';

为了使用户能够点击应用程序中的图片,我们在ChatMessage类的build()方法中使用新的GestureDetector控件替换Image.network函数,以及添加一个导航器(Navigator)。

class ChatMessage extends StatelessWidget {//...@overrideWidget build(BuildContext context) {return new SizeTransition(//...new Container(margin: const EdgeInsets.only(top: 5.0),child: snapshot.value['imageUrl'] != null ?new GestureDetector(onTap: (){Navigator.of(context).push( new MaterialPageRoute<Null>(builder: (BuildContext context) {return new ImageZoomable();}));},child: new Image.network(snapshot.value['imageUrl'],width: 250.0,),):new Text(snapshot.value['text']),)//...);}
}

GestureDetector控件是检测手势的控件,可以识别用户的各种操作手势。比如上面代码中的onTap属性可以识别用户的点击操作,并调用回调处理事件。Navigator是导航器,使用户能从当前屏幕平滑过渡到另一个屏幕,具体内容可以查看《Flutter进阶—路由和导航》。在用户点击图片时,我们使用导航器将用户从聊天屏幕过渡到图片查看屏幕。

如果我们只是想显示图片,不需要放大、缩小和移动图片,可以使用Image控件显示图像。具体实现可以看《Flutter基础—常用控件之图片》。在我们的项目中,想要实现放大、缩小和移动图片的功能,需要使用paintImage函数将图像绘制到画布中的给定矩形内。使用参数canvas设置将画出图像的画布,参数rect设置画布中的矩形,参数image设置要画在画布上的图像。如下面的代码,在image_zoomable.dart文件中添加_ImageZoomablePainter类。

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:ui' as ui;//...class _ImageZoomablePainter extends CustomPainter {const _ImageZoomablePainter({this.image, this.offset, this.zoom});final ui.Image image;final Offset offset;final double zoom;@overridevoid paint(Canvas canvas, Size size) {paintImage(canvas: canvas, rect: offset & (size * zoom), image: image);}@overridebool shouldRepaint(_ImageZoomablePainter old) {return old.image != image || old.offset != offset || old.zoom != zoom;}
}

_ImageZoomablePainter类的构造函数有三个参数,分别是ui.Image类型的image变量,存储用于原始解码的图像数据(像素),Offset类型的offset变量和double类型的_zoom变量,用于计算矩形的大小。关于动画的相关内容,可以查看《Flutter进阶—实现动画效果(一)》。

ImageZoomable类定义中,添加一个成员变量来存储图像文件。

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

现在将图像文件传到新的_ImageZoomableState实例,由于paintImage函数的image参数需要的是原始解码图像数据(像素),即ui.Image对象,所以我们需要将ImageProvider转成ui.Image对象。ImageStream表示ui.Image对象及其缩放(由ImageInfo对象表示)。

class _ImageZoomableState extends State<ImageZoomable> {ImageStream _imageStream;//...void _resolveImage() {_imageStream = widget.image.resolve(createLocalImageConfiguration(context));_imageStream.addListener(_handleImageLoaded);}//...
}

我们通过抽像类widget找到ImageZoomable类的成员变量imagecreateLocalImageConfiguration函数基于给定的上下文创建一个ImageConfiguration类实例。ImageProviderresolve方法使用给定的ImageConfiguration处理图像来源,返回ImageStream实例。ImageStream类的addListener方法添加一个监听器回调,当具体的ImageInfo对象可用时调用。

接下来,我们在_ImageZoomableState类中添加_handleImageLoaded方法,作为ImageInfo对象可用时的回调。

class _ImageZoomableState extends State<ImageZoomable> {ImageStream _imageStream;ui.Image _image;//...void _handleImageLoaded(ImageInfo info, bool synchronousCall) {setState(() {_image = info.image;});}//...
}

ImageInfo表示一个ui.Image对象与其对应的缩放比例,其image属性返回原始图像像素。

我们现在需要调用_resolveImage方法来加载图像,InheritedWidget会在当前State对象的依赖关系发生变化时调用,例如,如果之前对build的调用引用了随后更改的InheritedWidget(控件的基类可以有效地将信息传播到树枝),则框架将调用此方法来通知此对象有关更改。

class _ImageZoomableState extends State<ImageZoomable> {//...@overridevoid didChangeDependencies() {_resolveImage();super.didChangeDependencies();}//...
}

为了防止图像缓存被刷新,我们需要使用reassemble方法。reassemble方法会在调试期间重组应用程序时调用,该方法应重新运行依赖于全局状态的任何初始化逻辑,例如,本地资源的图像加载,因为本地资源可能已经更改。

class _ImageZoomableState extends State<ImageZoomable> {//...@overridevoid reassemble() {_resolveImage();super.reassemble();}//...
}

我们需要dispose方法在当前对象永久从树中删除时调用ImageStreamremoveListener方法,以停止监听新的具体的ImageInfo对象。

class _ImageZoomableState extends State<ImageZoomable> {//...@overridevoid dispose() {_imageStream.removeListener(_handleImageLoaded);super.dispose();}//...
}

现在我们修改一下_ImageZoomableState类的build方法,使图像绘制在屏幕上。

class _ImageZoomableState extends State<ImageZoomable> {//...@overrideWidget build(BuildContext context) {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,)));}//...
}

Transform是在绘制子控件之前应用转换的控件,其transform属性在绘制期间转换子控件的矩阵,Matrix4的构建函数Matrix4.diagonal3Values()会创建一个比例矩阵。CustomPaint控件提供了一个在绘制阶段绘制的画布,CustomPaintpainter属性设置在子控件中绘制的画家,也就是项目中的_ImageZoomablePainter

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    优化输入体验 在进行下一步之前,我们先优化一下注册的体验: 正在输入注册信息时,点击屏幕空白部分,清除当前文本输入框的焦点,同时收起键盘. 正在输入注册信息时,直接收起键盘,再点击空白部分,清除当前文 ...

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

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

最新文章

  1. 数字资产交易所IM即时通讯社交系统APP开发
  2. Vue中的Js动画与Velocity.js 的结合
  3. 安卓EditText
  4. [转载] Java 语言中的实例初始化块 ( IIB) 详解
  5. android 抽屉侧滑冲突,利用DrawerLayout和触摸事件分发实现抽屉侧滑效果
  6. Python 根据百度 API 获得经纬度,根据经纬度计算城市间距离
  7. 前端后台学习笔记汇杂
  8. 一个神奇的???whatever~~
  9. oracle知识小结二
  10. cocos creator入门教程(十八)—— creator_Director对象与资源加载策略
  11. 苏州新导蓝牙定位系统原理精确解读,三分钟看懂蓝牙定位
  12. shell粘贴复制快捷键
  13. uni-app实现一键登录
  14. WebProxy - 网站转发代理
  15. わたしたちの田村くん
  16. Python如何释放内存
  17. Android仿微信拍摄、录制视频,以及视频播放(基于JCameraView和GSYVideoPlayer)
  18. php利用PDO对数据库的操作练习
  19. Js 根据经纬度坐标计算方位角
  20. 常用的自动化管理工具

热门文章

  1. ~~染色法判别二分图
  2. Python监控文件变化:watchdog
  3. InteliJ Idea通过maven创建webapp
  4. Java中this关键词与构造函数,构造代码块的理解
  5. 【Qt串口调试助手】1.8 - 修改Qt应用图标和窗口图标
  6. 计算两个日期的时间间隔 python
  7. 881.BoatstoSavePeople
  8. 【ASP.NET】HTTP中的 get 和 post 请求
  9. Redis六种底层数据结构
  10. JS实现图片翻书效果