【-Flutter组件篇- 】1.20新增组件InteractiveViewer

0、前言

Flutter更新到1.20,出了一个新组件InteractiveViewer,主要对移动、缩放的手势交互进行封装,简化使用。

移动 | 缩放 ---|--- |

家族: StatefulWidget 源码行数: 1207 依赖的核心组件: GestureDetector、Transform、ClipRect、OverflowBox

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时,按下后只能沿某个轴向进行拖动(如右图)

  • 示例代码

```dart 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


  • 示例代码

```dart 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, 子组件将被赋予无限的约束。

```dart 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<Matrix4> {


  • 示例代码

``` 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.navigatebefore, 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.navigatenext, 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,
  ),
);

} }

```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值