Flutter更新到1.20,出了一个新组件
InteractiveViewer
,主要对移动、缩放的手势交互进行封装,简化使用。
家族: StatefulWidget
源码行数: 1207
依赖的核心组件: GestureDetector、Transform、ClipRect、OverflowBox
1 | 2 | 3 |
---|---|---|
属性名 | 类型 | 默认值 | 简介 |
---|---|---|---|
alignPanAxis | bool | false | 沿轴拖动 |
boundaryMargin | EdgeInsets | EdgeInsets.zero | 边界边矩 |
panEnabled | bool | true | 是否可平移 |
child | Widget | @required | 子组件 |
移动 | 缩放 |
---|---|
boundaryMargin
是可移动的限定边距。默认是EdgeInsets.zero,即被定死,不能移动panEnabled
可指定是否支持移动,默认为truealignPanAxis
指定是否沿轴拖动,默认为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'),
),
),
);
}
}
复制代码
属性名 | 类型 | 默认值 | 简介 |
---|---|---|---|
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'),
),
),
);
}
}
复制代码
属性名 | 类型 | 默认值 | 简介 |
---|---|---|---|
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];
}
复制代码
属性名 | 类型 | 默认值 | 简介 |
---|---|---|---|
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());
}
}
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;
});
}
}
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,
),
);
}
}