【-Flutter组件篇- 】1.20新增组件InteractiveViewer
0、前言
Flutter更新到1.20,出了一个新组件
InteractiveViewer
,主要对移动、缩放的手势交互进行封装,简化使用。
家族: StatefulWidget
源码行数: 1207
依赖的核心组件: GestureDetector、Transform、ClipRect、OverflowBox
- 此组件已加入FlutterUnit,欢迎star~
1 |
2 |
3 |
---|---|---|
1、子组件的移动
属性名 |
类型 |
默认值 |
简介 |
---|---|---|---|
alignPanAxis |
bool |
false |
沿轴拖动 |
boundaryMargin |
EdgeInsets |
EdgeInsets.zero |
边界边矩 |
panEnabled |
bool |
true |
是否可平移 |
child |
Widget |
@required |
子组件 |
移动 |
缩放 |
---|---|
- 如左图,灰色区域是InteractiveViewer的上级区域。
-
boundaryMargin
是可移动的限定边距。默认是EdgeInsets.zero,即被定死,不能移动 -
panEnabled
可指定是否支持移动,默认为true -
alignPanAxis
指定是否沿轴拖动,默认为false(左图)。当为true时,按下后只能沿某个轴向进行拖动(如右图)
示例代码
class InteractiveViewerDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 150,
color: Colors.grey.withAlpha(33),
child: InteractiveViewer(
// alignPanAxis: true,
panEnabled: true,
boundaryMargin: EdgeInsets.all(40.0),
child: Container(
child: Image.asset('assets/images/caver.jpeg'),
),
),
);
}
}
复制代码
2、子组件缩放
属性名 |
类型 |
默认值 |
简介 |
---|---|---|---|
maxScale |
double |
2.5 |
最大放大倍数 |
minScale |
double |
0.8 |
最小缩小倍数 |
scaleEnabled |
bool |
true |
是否可缩放 |
-
scaleEnabled
为是否开启缩放,maxScale和minScale分别确定放大缩小的倍数限值。
估计百分之九十的人都很难触发缩放效果,昨天在群里讨论后。Alex给出了手势触发情况: 先把一只手指放上去,边移动边放第二只。
同时提出了一个issues: [InteractiveViewer] Hard to scale when two fingers tap down at the same
示例代码
class InteractiveViewerDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 150,
color: Colors.grey.withAlpha(33),
child: InteractiveViewer(
// alignPanAxis: true,
boundaryMargin: EdgeInsets.all(40.0),
maxScale: 2.5,
minScale: 0.3,
panEnabled: true,
scaleEnabled: true,
child: Container(
child: Image.asset('assets/images/caver.jpeg'),
),
),
);
}
}
复制代码
3、constrained属性
属性名 |
类型 |
默认值 |
简介 |
---|---|---|---|
constrained |
bool |
true |
受约束的 |
关于constrained属性,源码中给了一个小demo。这里的表格可以
上下滚动,左右滑动
。constrained默认为true,当子组件比InteractiveViewer区域大时,将constrained设为false, 子组件将被赋予无限的约束。
class InteractiveViewerDemo2 extends StatelessWidget {
Widget build(BuildContext context) {
const int _rowCount = 20;
const int _columnCount = 4;
return Container(
width: 300,
height: 200,
child: InteractiveViewer(
constrained: false,
scaleEnabled: false,
child: Table(
columnWidths: <int, TableColumnWidth>{
for (int column = 0; column < _columnCount; column += 1)
column: const FixedColumnWidth(150.0),
},
children: buildRows(_rowCount, _columnCount),
),
),
);
}
List buildRows(int rowCount, int columnCount) {
return [
for (int row = 0; row < rowCount; row += 1)
TableRow(
children: [
for (int column = 0; column < columnCount; column += 1)
Container(
margin: EdgeInsets.all(2),
height: 50,
alignment: Alignment.center,
color: _colorful(row,column),
child: Text('($row,$column)',style: TextStyle(fontSize: 20,color: Colors.white),),
),
],
),
];
}
final colors = [Colors.red,Colors.yellow,Colors.blue,Colors.green];
final colors2 = [Colors.yellow,Colors.blue,Colors.green,Colors.red];
_colorful(int row, int column ) => row % 2==0?colors[column]:colors2[column];
}
复制代码
4、回调事件
属性名 |
类型 |
默认值 |
简介 |
---|---|---|---|
onInteractionEnd |
GestureScaleEndCallback |
null |
交互结束回调 |
onInteractionStart |
GestureScaleStartCallback |
null |
交互开始回调 |
onInteractionUpdate |
GestureScaleUpdateCallback |
null |
交互更新回调 |
-
onInteractionStart
当触碰时,onInteractionStart 会回调ScaleStartDetails
对象focalPoint
是相对于屏幕左上角的偏移量。localFocalPoint
是相对于父容器区域左上角的偏移量。
ScaleStartDetails(
focalPoint: Offset(306.0, 168.7),
localFocalPoint: Offset(50.4, 63.7)
)
-
onInteractionUpdate
当手指滑动时,onInteractionUpdate 会回调ScaleUpdateDetails
对象focalPoint
是相对于屏幕左上角的偏移量。localFocalPoint
是相对于父容器区域左上角的偏移量。scale
缩放量。horizontalScale
水平缩放量。verticalScale
竖直缩放量。rotation
旋转量。------这里说明能监听到旋转量
onInteractionUpdate----
ScaleUpdateDetails(
focalPoint: Offset(6.4, 13.7),
localFocalPoint: Offset(6.4, 13.7),
scale: 1.0,
horizontalScale: 1.0,
verticalScale: 1.0,
rotation: 0.0
)
-
onInteractionEnd
当手指滑动时,onInteractionEnd 会回调ScaleEndDetails
对象velocity
水平和竖直方向的速度量。
onInteractionEnd----
ScaleEndDetails(velocity: Velocity(0.0, 0.0))
示例代码
class InteractiveViewerDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 150,
color: Colors.grey.withAlpha(33),
child: InteractiveViewer(
boundaryMargin: EdgeInsets.all(40.0),
maxScale: 2.5,
minScale: 0.3,
panEnabled: true,
scaleEnabled: true,
child: Container(
child: Image.asset('assets/images/caver.jpeg'),
),
onInteractionStart: _onInteractionStart,
onInteractionUpdate: _onInteractionUpdate,
onInteractionEnd: _onInteractionEnd,
),
);
}
void _onInteractionStart(ScaleStartDetails details) {
print('onInteractionStart----' + details.toString());
}
void _onInteractionUpdate(ScaleUpdateDetails details) {
print('onInteractionUpdate----' + details.toString());
}
void _onInteractionEnd(ScaleEndDetails details) {
print('onInteractionEnd----' + details.toString());
}
}
5.变换控制器 transformationController
属性名 |
类型 |
默认值 |
简介 |
---|---|---|---|
transformationController |
TransformationController |
null |
变化控制器 |
可以通过
transformationController
进行变换控制,如上面通过按钮进行复位、移动TransformationController
是一个Matrix4
泛型的ValueNotifier 所以可以通过改变TransformationController.value来对子组件进行高级的变换操作,Matrix4
的强大,你懂得...
class TransformationController extends ValueNotifier {
示例代码
class InteractiveViewerDemo3 extends StatefulWidget {
@override
_InteractiveViewerDemo3State createState() => _InteractiveViewerDemo3State();
}
class _InteractiveViewerDemo3State extends State
with SingleTickerProviderStateMixin {
final TransformationController _transformationController =
TransformationController();
Animation _animationReset;
AnimationController _controllerReset;
void _onAnimateReset() {
_transformationController.value = _animationReset.value;
if (!_controllerReset.isAnimating) {
_animationReset?.removeListener(_onAnimateReset);
_animationReset = null;
_controllerReset.reset();
}
}
void _animateResetInitialize() {
_controllerReset.reset();
_animationReset = Matrix4Tween(
begin: _transformationController.value,
end: Matrix4.identity(),
).animate(_controllerReset);
_animationReset.addListener(_onAnimateReset);
_controllerReset.forward();
}
void _animateResetStop() {
_controllerReset.stop();
_animationReset?.removeListener(_onAnimateReset);
_animationReset = null;
_controllerReset.reset();
}
void _onInteractionStart(ScaleStartDetails details) {
if (_controllerReset.status == AnimationStatus.forward) {
_animateResetStop();
}
}
@override
void initState() {
super.initState();
_controllerReset = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
}
@override
void dispose() {
_controllerReset.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Wrap(
direction: Axis.vertical,
spacing: 10,
crossAxisAlignment: WrapCrossAlignment.center,
alignment: WrapAlignment.center,
children: [
Container(
height: 150,
color: Colors.grey.withAlpha(33),
child: InteractiveViewer(
boundaryMargin: EdgeInsets.all(40),
transformationController: _transformationController,
minScale: 0.1,
maxScale: 1.8,
onInteractionStart: _onInteractionStart,
child: Container(
child: Image.asset('assets/images/caver.jpeg'),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildButton(),
_buildButton2(),
_buildButton3(),
],
)
],
);
}
Widget _buildButton() {
return MaterialButton(
child: Icon(
Icons.refresh,
color: Colors.white,
),
color: Colors.green,
shape: CircleBorder(
side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),
),
onPressed: _animateResetInitialize);
}
var _x = 0.0;
Widget _buildButton2() {
return MaterialButton(
child: Icon(
Icons.navigate_before,
color: Colors.white,
),
color: Colors.green,
shape: CircleBorder(
side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),
),
onPressed: () {
var temp = _transformationController.value.clone();
temp.translate(_x - 4);
_transformationController.value = temp;
});
}
Widget _buildButton3() {
return MaterialButton(
child: Icon(
Icons.navigate_next,
color: Colors.white,
),
color: Colors.green,
shape: CircleBorder(
side: BorderSide(width: 2.0, color: Color(0xFFFFDFDFDF)),
),
onPressed: () {
var temp = _transformationController.value.clone();
temp.translate(_x + 4);
_transformationController.value = temp;
});
}
}
6.InteractiveViewer的核心源码
Listener组件 + GestureDetector组件
实现手势交互相关功能及回调Transform组件
通过transformationController的Matrix4
进行变换 如果constrained=false
外会附加一层ClipRect+OverflowBox
。
@override
Widget build(BuildContext context) {
Widget child = Transform(
transform: _transformationController.value,
child: KeyedSubtree(
key: _childKey,
child: widget.child,
),
);
if (!widget.constrained) {
child = ClipRect(
child: OverflowBox(
alignment: Alignment.topLeft,
minWidth: 0.0,
minHeight: 0.0,
maxWidth: double.infinity,
maxHeight: double.infinity,
child: child,
),
);
}
// A GestureDetector allows the detection of panning and zooming gestures on
// the child.
return Listener(
key: _parentKey,
onPointerSignal: _receivedPointerSignal,
child: GestureDetector(
behavior: HitTestBehavior.opaque, // Necessary when panning off screen.
onScaleEnd: _onScaleEnd,
onScaleStart: _onScaleStart,
onScaleUpdate: _onScaleUpdate,
child: child,
),
);
}
}
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux下Python脚本自启动和定时启动的详细步骤
- linux后台执行命令&和nohup的具体使用方法
- Linux修改hostname与免密码登录的方法
- Linux启动与停止spring boot工程的脚本示例
- 在Linux上如何检查用户所属组详解
- Linux中移除(删除)符号链接的命令
- Linux定时备份数据库到指定邮箱的方法
- 详解nohup /dev/null 2>&1 含义的使用
- centOS7安装jdk1.8的方法
- 你知道一台Linux服务器可以负载多少个连接吗
- Linux环境下安装Nginx及其使用
- CentOS8中的nmcli使用详解
- 在Linux中使用history命令的方法
- Linux服务器部署JavaWeb项目完整教程
- centos 6 安装vsftpd与PAM虚拟用户的方法