背景
学习一个新的框架,我还是更喜欢在实践中去学习,最近在学习 Flutter 时,我就从一个图片浏览器来做起,在实现过程中,我发现用 Flutter 来实现 PictureView 非常简单,因为框架已经给我们做了大部分的事情,不像在原生 Android 上做的这么复杂,实现的效果大概如下:
效果图代码
因为内容很简单,直接先把源码贴出来,然后大概说一下里面的关键点就行了。
import 'package:flutter/material.dart';
import 'dart:io';
class PictureView extends StatefulWidget {
final String _path;
PictureView(this._path);
@override
State<StatefulWidget> createState() => _PictureViewState();
}
class _PictureViewState extends State<PictureView> with TickerProviderStateMixin {
double _scale = 1.0;
double _tmpScale = 1.0;
double _moveX = 0.0;
double _tmpMoveX = 0.0;
double _moveY = 0.0;
double _tmpMoveY = 0.0;
double _rotation = 0.0;
double _tmpRotation = 0.0;
Offset _tmpFocal = Offset.zero;
AnimationController _animationController;
Animation<double> _values;
@override
void initState() {
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 120));
// Tween 将动画的 0 - 1 的值映射到我们设置的范围内
_values = Tween(begin: 1.0, end: 0.0).animate(_animationController);
_animationController.addListener(() {
setState(() {
// 通过动画逐帧还原位置
_moveX = _tmpMoveX * _values.value;
_moveY = _tmpMoveY * _values.value;
_scale = (_tmpScale - 1) * _values.value + 1;
_rotation = _tmpRotation * _values.value;
});
});
super.initState();
}
@override
void dispose() {
_animationController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// 配置 Matrix
Matrix4 matrix4 = Matrix4.identity()
..scale(_scale, _scale)
..translate(_moveX, _moveY)
..rotateZ(_rotation);
return Scaffold(
body: GestureDetector(
onDoubleTap: () {
if (!_animationController.isAnimating) {
_tmpMoveX = _moveX;
_tmpMoveY = _moveY;
_tmpScale = _scale;
_tmpRotation = _rotation;
_animationController.reset();
_animationController.forward();
}
},
onScaleStart: (details) {
if (!_animationController.isAnimating) {
_tmpFocal = details.focalPoint;
_tmpMoveX = _moveX;
_tmpMoveY = _moveY;
_tmpScale = _scale;
_tmpRotation = _rotation;
}
},
onScaleUpdate: (details) {
if (!_animationController.isAnimating) {
setState(() {
_moveX = _tmpMoveX + (details.focalPoint.dx - _tmpFocal.dx) / _tmpScale;
_moveY = _tmpMoveY + (details.focalPoint.dy - _tmpFocal.dy) / _tmpScale;
_scale = _tmpScale * details.scale;
_rotation = _tmpRotation + details.rotation;
print(_rotation);
});
}
},
child: Container(
color: Colors.black,
child: Center(
child: Hero(
tag: widget._path,
child: Transform(
alignment: FractionalOffset.center,
transform: matrix4,
child: Image.file(
File(widget._path),
),
),
),
),
),
),
);
}
}
这里面比较有意思的就是 GestureDetector 这个手势监控控件,因为我们要实现拖拽和缩放,所以理所当然的我就想要实现了 onPan*** 和 onScale*** 两种接口来实现这个效果,结果发现,这两种接口不能同时实现,他们存在冲突,同时实现会直接抛异常,于是我想到了用 GestureDetector 嵌套 GestureDetector 来实现,外部的 GestureDetector 来实现缩放逻辑,内部的 GestureDetector 来实现 拖动逻辑,这样最终实现了我想要的效果,但是在通过 onScale*** 接口来实现旋转的时候,发现旋转的逻辑又与拖动的逻辑冲突了,非常的恶心,这里更加神奇的是,旋转手势的参数 rotation 竟然通过 onScale*** 接口提供,给人一种东拼西凑的感觉。
既然多个手势无法合并来用,而 onScale*** 接口已经可以帮我们实现缩放和旋转了,那我们就想办法去获得拖动量就可以了,这里我想到了可以通过缩放焦点的位置变化来间接的去计算拖动距离,具体的实现就像上方代码一样。
总结
其实我认为学习一个新技术最好的方法就是在了解了它的基本规则之后就尝试去写一些 DEMO,然后在 DEMO 中发现为题,逐步的去细化,了解原理,这样学习的过程就不会枯燥乏味了。
吐槽
这里很想吐槽一下 Dart 中的 “;” ,相比较于其它语法来说真的是很奇怪,语法都已经这么简洁了为什么还非要写一个 “;”,而且编辑器还不会自动帮你补上 ,特别是在写 Flutter 这种多嵌套的结构的时候,真的很麻烦,真的希望 Dart 可以多借鉴一下 Kotlin 的语法,个人还是蛮喜欢 Kotlin 的,哈哈。
网友评论
github: https://github.com/renancaraujo/photo_view