我们的应用程序现在可以点击查看图像,但还没有实现查看时放大、缩小与移动图像。要实现这个功能,需要监听用户在图像上的操作,并调用相应的回调处理用户操作。我们先将Transform
控件从_ImageZoomableState
的build
方法中拆分出来。在_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,
)
)
);
}
//...
}
修改_ImageZoomableState
的build
方法,使用GestureDetector
控件包装_drawImage
方法中的Transform
控件,我们可以使用GestureDetector
控件监听用户的各种操作手势。
class _ImageZoomableState extends State<ImageZoomable> {
//...
@override
Widget 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 {
//...
@override
Widget 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> {
//...
@override
Widget 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> {
//...
@override
Widget build(BuildContext context) {
return new GestureDetector(
child: _drawImage(),
onScaleStart: _handleScaleStart,
onScaleUpdate: (d) => _handleScaleUpdate(context.size, d),
);
}
//...
}
目前用户在查看图像时,需要点击系统的后退按钮才能返回到聊天屏幕。在大部分应用程序中,在查看图像时点击即可返回聊天屏幕,我们也可以这么做。在ImageZoomable
类定义中声明一个GestureTapCallback
类型的成员变量onTap
,GestureTapCallback
类型表示点击发生时的回调函数。
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> {
//...
@override
Widget 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 {
//...
@override
Widget 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();
},
);
}
));
},
//...
);
}
}
现在图像查看,且查看时可以缩放、移动图像的功能已经完成了!