custom_interactive_viewer 0.1.0 copy "custom_interactive_viewer: ^0.1.0" to clipboard
custom_interactive_viewer: ^0.1.0 copied to clipboard

A highly customizable alternative for Flutter's InteractiveViewer with advanced keyboard navigation, gesture controls, rotation support, and programmatic transformations.

Custom Interactive Viewer #

A Flutter camera and input package for images, documents, maps, whiteboards and diagrams. Supports mouse, touch, trackpad, wheel and keyboard input, focal-point zoom, rotation, camera animation and configurable bounds.

Requires Flutter 3.41 / Dart 3.11 or newer.

dependencies:
  custom_interactive_viewer: ^0.1.0

Finite content #

CustomInteractiveViewer(
  child: Image.asset('assets/map.png'),
  zoomConfig: const ZoomConfig(minScale: .1, maxScale: 8),
)

The ordinary constructor measures its child and uses directional alignment. contentSize or contentSizeGetter can override measurement. Use InteractionConfig(constrainBounds: true) to constrain finite content. Existing finite-content usage remains supported; see migration notes for corrected lifecycle and animation behavior.

A world canvas #

final camera = CustomInteractiveViewerController(
  initialOffset: const Offset(300, 200),
);

CustomInteractiveViewer.world(
  controller: camera,
  viewportBuilder: (context, viewport) {
    // This point can be anywhere, including left/above the origin.
    final anchor = viewport.worldToViewport(const Offset(-100, -50));
    return Stack(children: [
      Positioned(
        left: anchor.dx,
        top: anchor.dy,
        child: const Text('A world-space annotation'),
      ),
    ]);
  },
)

The builder receives a viewport-sized area and an immutable ViewerViewport. It supplies worldToViewport, viewportToWorld, worldToViewportMatrix, worldCorners and visibleWorldBounds. Use the matrix with Canvas.transform when painting world-scaled/rotated content. Mapping an anchor alone intentionally keeps a widget upright and at a constant screen size.

The world origin is stable across resize and RTL changes. The camera does not mirror coordinates or automatically center when mounted; text still inherits Flutter Directionality. No oversized canvas or repeated origin rebasing is used. The application builds/hit-tests its content and can use the viewport bounds to cull offscreen objects. The viewer knows nothing about nodes or edges.

Camera commands #

camera.pan(const Offset(40, 20), animate: false);
camera.zoom(factor: .2, focalPoint: const Offset(200, 100));
camera.rotate(.25, focalPoint: const Offset(200, 100));
await camera.zoomToRegion(
  const Rect.fromLTWH(-500, -300, 1000, 600), viewportSize,
);
await camera.centerOnRect(const Rect.fromLTWH(-200, -100, 80, 60));
await camera.reset();

zoom uses a relative increment: .2 requests 1.2 times the current scale. Focal points and pan deltas are viewport-local pixels. World/content positions are converted with the controller or viewport snapshot. Animated commands need an attached viewer (or an explicit ticker provider); immediate commands work headlessly. New direct manipulation cancels active camera animation. Interrupted animation futures complete normally. Reset restores the initial camera values.

Free navigation is the default. Finite widgets can use ViewportBoundsBehavior. World canvases can use:

InteractionConfig(
  boundsBehavior: WorldBoundsBehavior(
    const Rect.fromLTWH(-1000, -800, 2000, 1600),
    margin: 100,
  ),
)

For content-relative navigation, update the supplied rectangle when the application's content bounds change. Fit/center remain explicit commands rather than persistent constraints. With rotation, bounds constrain the transformed axis-aligned envelope, not exact polygon containment.

Input and ownership #

ZoomConfig, InteractionConfig, and KeyboardConfig configure camera inputs. Use ScrollMode to restrict pan axes and optional interaction behaviors for snapping/custom constraints. Browser pinch signals are supported without a physical Ctrl-key event. A controller belongs to one mounted viewer at a time; replacing it detaches the old controller. Dispose controllers you create. Child controls and editing gestures participate in Flutter's gesture arena. The host application owns node dragging, selection and other domain gestures.

Example and verification #

Run flutter run -d chrome from example for the camera playground. It exposes free/bounded navigation, negative-coordinate content, rotation, RTL text, fit/reveal and reset. Run flutter test at the package root for camera geometry, gesture and lifecycle regression tests. Synthetic input tests do not replace physical device testing across every operating system.

9
likes
150
points
1.7k
downloads

Documentation

API reference

Publisher

verified publisherali-hnaineh.dev

Weekly Downloads

A highly customizable alternative for Flutter's InteractiveViewer with advanced keyboard navigation, gesture controls, rotation support, and programmatic transformations.

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, vector_math

More

Packages that depend on custom_interactive_viewer