InteractiveViewer Widget - Flutter Guide By Flutter Widget

InteractiveViewer Widget – Flutter Widget Guide By Flutter Agency

What is InteractiveViewer Widget in Flutter?

This release of Flutter 1.20  introduces a new widget, the InteractiveViewer Widget. It is designed for building common kinds of interactivity into the app, like pan, zoom, and drag ’n’ drop, even in the face of resizing.

Kindly check out the API documentation to understand how to integrate InteractiveViewer Widget into your own app.

Curious readers like you can take a look at it how it was designed and developed, you can see a presentation by the author for Chicago Flutter on YouTube.

Default Constructor for it will look like below:

InteractiveViewer({
  key: key,
  clipBehavior: Clip.hardEdge,
  PanAxis panAxis: PanAxis.free,
  EdgeInsets boundaryMargin: EdgeInsets.zero,
  bool constrained: true,
  double maxScale: 2.5,
  double minScale: 0.8,
  double interactionEndFrictionCoefficient: _kDrag,
  GestureScaleEndCallback? onInteractionEnd,
  GestureScaleStartCallback? onInteractionStart,
  GestureScaleUpdateCallback? onInteractionUpdate,
  bool panEnabled: true,
  bool scaleEnabled: true,
  double scaleFactor: kDefaultMouseScrollToScaleFactor,
  TransformationController? transformationController,
  Alignment? alignment,
  bool trackpadScrollCausesScale: false,
  required Widget child,
})

In the Above constructor, all fields marked with @required must not be empty.

Properties:

  • bool: If true, panning is only allowed in the direction of the horizontal axis or the vertical axis. In other words, when this is true, diagonal panning is not allowed. A single gesture begun along one axis cannot also cause panning along the other axis without stopping and beginning a new gesture. This is a common pattern in tables where data is displayed in columns and rows.
  • EdgeInsets boundaryMargin: A margin for the visible boundaries of the child. Any transformation that results in the viewport being able to view outside of the boundaries will be stopped at the boundary. The boundaries do not rotate with the rest of the scene, so they are always aligned with the viewport.
  • bool constrained: Whether the normal size constraints at this point in the widget tree are applied to the child. If set to false, then the child will be given infinite constraints. This is often useful when a child should be bigger than InteractiveViewer Widget. The default value will be true.
  • double maxScale: The maximum allowed scale. The scale will be clamped between this and minScale inclusively. The default value will be 2.5. It can not be null and must be greater than zero and greater than minScale.
  • double minScale: The minimum allowed scale. The scale will be clamped between this and maxScale inclusively. The default value will be 0.8. It cannot be null and must be a finite number greater than zero and less than maxScale.
  • GestureScaleStartCallback onInteractionStartonInteractionStart will be called when the user begins a pan or scale gesture on the widget. It will be called even if the interaction is disabled with panEnabled or scaleEnabled. A GestureDetector wrapping the InteractiveViewer will not respond to GestureDetector.onScaleStart,GestureDetector.onScaleUpdate, and GestureDetector.onScaleEnd. Use onInteractionStartonInteractionUpdate, and onInteractionEnd to respond to those gestures.
  • GestureScaleEndCallback onInteractionEndonInteractionEnd will be called when the user ends a pan or scale gesture on the widget. It will be called even if the interaction is disabled with panEnabled or scaleEnabled. A GestureDetector wrapping the InteractiveViewer will not respond to GestureDetector.onScaleStartGestureDetector.onScaleUpdate, and GestureDetector.onScaleEnd.Use onInteractionStartonInteractionUpdate, and onInteractionEnd to respond to those gestures.
  • GestureScaleUpdateCallback onInteractionUpdateonInteractionUpdate will be called when the user updates a pan or scale gesture on the widget.It will be called even if the interaction is disabled with panEnabled or scaleEnabled. A GestureDetector wrapping the InteractiveViewer will not respond to GestureDetector.onScaleStartGestureDetector.onScaleUpdate, and GestureDetector.onScaleEnd. Use onInteractionStartonInteractionUpdate, and onInteractionEnd to respond to those gestures.
  • bool panEnabled: If false, the user will be prevented from panning. Defaults to true.

How to use InteractiveViewer Widget in Flutter?

The following code snippet tells us how to implement InteractiveViewer Widget in Flutter.

import 'dart:ui' as ui;

import 'package:flutter/material.dart';

const _strokeWidth = 8.0;


class InteractiveViewer extends StatefulWidget {
  @override
  _InteractiveViewerState createState() =>
      _InteractiveViewerState();
}

class _InteractiveViewerState extends State<InteractiveViewer> {
  static const _feedbackSize = Size(100.0, 100.0);
  static const _padding = 16.0;

  static final _decoration = BoxDecoration(
    border: Border.all(
      color: Colors.blue,
      width: _strokeWidth,
    ),
    borderRadius: BorderRadius.circular(12),
  );

  Offset _lastDropOffset = Offset.zero;
  int _lastDropIndex = -1;

  Widget _buildSourceRowChild(int index) => Expanded(
      child: Padding(
          padding: EdgeInsets.all(_padding),
          child: Draggable<int>(
              data: index,
              dragAnchor: DragAnchor.pointer,
              feedback: Transform.translate(
                  offset: Offset(
                      -_feedbackSize.width / 2.0, -_feedbackSize.height / 2.0),
                  child: Container(
                      decoration: _decoration,
                      width: _feedbackSize.width,
                      height: _feedbackSize.height)),
              child: Container(
                  decoration: _decoration,
                  child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text('drag me'),
                        Text('$index', style: TextStyle(fontSize: 32.0))
                      ])))));

  void _handleAcceptWithDetails(
      BuildContext dragTargetContext, DragTargetDetails details) {
    // Convert global to local coordinates.
    RenderBox renderBox = dragTargetContext.findRenderObject();
    final localOffset = renderBox.globalToLocal(details.offset);
    setState(() {
      _lastDropOffset = localOffset;
      _lastDropIndex = details.data;
    });
  }

  Widget _buildDragTargetChild() => Padding(
      padding: EdgeInsets.all(_padding),
      child: Container(
          decoration: _decoration,
          // Note use of builder to get a context for the [DragTarget] which is
          // available to pass to [_handleAcceptWithDetails].
          child: Builder(
              builder: (targetContext) => DragTarget<int>(
                  builder: (_, candidateData, __) => Container(
                      color: candidateData.isNotEmpty
                          ? Color(0x2200FF00)
                          : Color(0x00000000),
                      child: CustomPaint(
                          painter: _Painter(_lastDropOffset, _lastDropIndex))),
                  onAcceptWithDetails: (details) =>
                      _handleAcceptWithDetails(targetContext, details)))));

  @override
  Widget build(BuildContext context) {
    return Material(
        child:
            Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
      Expanded(
          flex: 1,
          child: Row(children: List<Widget>.generate(3, _buildSourceRowChild))),
      Expanded(flex: 2, child: _buildDragTargetChild())
    ]));
  }
}

class _Painter extends CustomPainter {
  static final _diameter = 24.0;

  static final _linePaint = Paint()
    ..style = PaintingStyle.stroke
    ..strokeWidth = _strokeWidth
    ..color = Colors.blue;

  static final _whiteFillPaint = Paint()
    ..style = PaintingStyle.fill
    ..color = Colors.white;

  static final _blueFillPaint = Paint()
    ..style = PaintingStyle.fill
    ..color = Colors.blue;

  final Offset _offset;
  final int _index;

  _Painter(this._offset, this._index);

  @override
  void paint(Canvas canvas, Size size) {
    if (_offset == null || _index == null) return;
    canvas.drawLine(
        Offset(_offset.dx, 0.0), Offset(_offset.dx, size.height), _linePaint);
    canvas.drawLine(
        Offset(0.0, _offset.dy), Offset(size.width, _offset.dy), _linePaint);

    canvas.drawCircle(_offset, _diameter + _strokeWidth, _blueFillPaint);
    canvas.drawCircle(_offset, _diameter, _whiteFillPaint);

    final paragraphBuilder =
        ui.ParagraphBuilder(ui.ParagraphStyle(textAlign: TextAlign.center))
          ..pushStyle(ui.TextStyle(
              fontStyle: FontStyle.normal,
              color: Colors.blue,
              fontSize: _diameter))
          ..addText('$_index');
    final paragraph = paragraphBuilder.build();
    paragraph.layout(ui.ParagraphConstraints(width: _diameter));
    canvas.drawParagraph(
        paragraph, _offset - Offset(_diameter / 2.0, _diameter / 2.0));
  }

  @override
  bool shouldRepaint(_Painter oldPainter) => false;
}

We will get output like below:

Interactive Widget - Flutter Widget Guide By Flutter Agency

Interactive Widget

Conclusion:

In this article, we have been through What is InteractiveViewer Widget in Flutter along with how to implement it in a Flutter.

Thanks for reading !!!
Keep Fluttering!!!

FlutterAgency.com is our portal Platform dedicated to Flutter Technology and Flutter Developers. The portal is full of cool resources from Flutter like Flutter Widget Guide, Flutter Projects, Code libs and etc.

FlutterAgency.com is one of the most popular online portal dedicated to Flutter Technology and daily thousands of unique visitors come to this portal to enhance their knowledge on Flutter.

Nirali Patel

Written by Nirali Patel

Nirali Patel is a dedicated Flutter developer with over two years of experience, specializing in creating seamless mobile applications using Dart. With a passion for crafting user-centric solutions, Nirali combines technical proficiency with innovative thinking to push the boundaries of mobile app development.

Leave a comment

Your email address will not be published. Required fields are marked *


ready to get started?

Fill out the form below and we will be in touch soon!

"*" indicates required fields

✓ Valid number ✕ Invalid number
our share of the limelight

as seen on