custom_interactive_viewer 0.2.0
custom_interactive_viewer: ^0.2.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 scene canvas #
final camera = CustomInteractiveViewerController(
initialOffset: const Offset(300, 200),
);
CustomInteractiveViewer.builder(
controller: camera,
viewportBuilder: (context, viewport) {
// This point can be anywhere, including left/above the origin.
final anchor = viewport.sceneToViewport(const Offset(-100, -50));
return Stack(children: [
Positioned(
left: anchor.dx,
top: anchor.dy,
child: const Text('A scene-space annotation'),
),
]);
},
)
The builder receives a viewport-sized area and an immutable ViewerViewport.
It supplies sceneToViewport, viewportToScene, sceneToViewportMatrix,
visibleSceneCorners and visibleSceneBounds. Use the matrix with Canvas.transform
when painting scene-scaled/rotated content. Mapping an anchor alone intentionally
keeps a widget upright and at a constant screen size.
The scene 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. Scene/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.
Navigation policies #
Free navigation is the default. Finite widgets can use
ViewportBoundsBehavior. Scene canvases can use:
InteractionConfig(
boundsBehavior: SceneBoundsBehavior(
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.