flutter_pip_view 0.0.2 copy "flutter_pip_view: ^0.0.2" to clipboard
flutter_pip_view: ^0.0.2 copied to clipboard

A reusable, production-ready Picture-in-Picture (PiP) floating view engine for Flutter. Turn any widget into a draggable, resizable, edge-snapping floating window with a few lines of code.

dashstack_poster

flutter_pip_view #

Turn any Flutter widget into a floating, draggable Picture-in-Picture window — like the little bubble you see during a video call — while the rest of your app keeps running underneath it.

PiPController.show(context, child: VideoCallView());

That's the whole API to get a floating window on screen. Everything else below is optional polish.

Requires: Dart >= 3.13.0 (see pubspec.yaml)

See it in action #

Draggable bubble Dragging it around
Draggable PiP bubble on the example home screen Draggable PiP bubble being dragged around the screen

Draggable PiP demo — drag, resize, and close a floating window

Try it yourself — everything above is running in example/.

Why you'd want this #

  • Bring your own content. No video player or calling SDK is baked in — show any widget you like.
  • Feels native. Drag, flick, and let go — the window settles into place with a real spring, not a flat animation.
  • Snaps to the nearest edge automatically when you release it.
  • Tap to go fullscreen, just like tapping a video call preview to bring back the full call.
  • Minimizes to a small bubble and expands back with a tap.
  • Accessible by default — keyboard support, focus ring, and respects the OS "reduce motion" setting.
  • Zero dependencies beyond the Flutter SDK.

Install #

dependencies:
  flutter_pip_view: ^0.1.0
flutter pub get

Quick start #

Wrap your app once — usually in MaterialApp.builder — so the PiP window can float above every screen:

MaterialApp(
  builder: (context, child) => PiPView(child: child!),
  home: const HomeScreen(),
)

PiPView stays invisible and does nothing until you call the PiP API, so this is safe to add even before you need it.

Then, from anywhere in your app:

// Show a small preview; hand fullscreen a distinct, real full-screen widget.
PiPController.show(
  context,
  child: VideoCallPreview(),
  fullscreenChild: VideoCallScreen(), // shown only while fullscreen
);

PiPController.moveTo(PiPPosition.bottomRight); // move it to a corner
PiPController.minimize();                      // collapse to a bubble
PiPController.expand();                         // restore it
PiPController.enterFullscreen();                // go fullscreen
PiPController.exitFullscreen();                 // back to floating
PiPController.hide();                           // close it

Customizing it #

Every option below is optional — const PiPConfig() works fine on its own.

PiPController.show(
  context,
  child: VideoCallView(),
  config: PiPConfig(
    width: 160,
    height: 220,
    minWidth: 120,
    minHeight: 160,
    maxWidth: 400,
    maxHeight: 500,
    borderRadius: 16,
    elevation: 8,
    draggable: true,
    resizable: true,
    snapToEdge: true,
    initialPosition: PiPPosition.bottomRight,
    margin: EdgeInsets.all(12),
    showCloseButton: true,
    showExpandButton: true,
    enableHapticFeedback: true,
    tapToFullscreen: true,       // tap the window to go fullscreen
    doubleTapToToggle: false,    // opt-in, for content with its own buttons
    resetOnClose: true,          // closing resets position/size/state
    showDragHandle: true,        // small grip when expanded
  ),
);

See the dartdoc on PiPConfig for the full list, including custom decoration, padding, closeButtonBuilder, expandButtonBuilder, and controlsBuilder.

Listening for events #

PiPController.show(
  context,
  child: VideoCallView(),
  callbacks: PiPCallbacks(
    onShow: () {},
    onHide: () {},
    onExpand: () {},
    onMinimize: () {},
    onFullscreenEnter: () {}, // e.g. pause the small preview, start the full one
    onFullscreenExit: () {},
    onDragStart: () {},
    onDragUpdate: (offset) {},
    onDragEnd: () {},
    onPositionChanged: (offset) {},
    onSizeChanged: (size) {},
  ),
);

Swapping in your own controls #

PiPConfig(
  controlsBuilder: (
    context,
    isMinimized,
    isFullscreen,
    onClose,
    onToggleMinimize,
    onToggleFullscreen,
  ) {
    return Positioned(
      bottom: 0,
      left: 0,
      right: 0,
      child: MyControlBar(
        onClose: onClose,
        onCollapse: onToggleMinimize,
        onToggleFullscreen: onToggleFullscreen,
      ),
    );
  },
)

A typical video-call screen keeps the small floating preview passive (no buttons) and puts the real controls (mic, camera, end call) inside a separate fullscreenChild — see FullCallScreen in example/lib/main.dart.

Good to know #

  • Closing really cleans up. hide() unmounts your child/ fullscreenChild once the close animation finishes, so any camera/audio stream, timer, or socket it holds gets disposed normally. Calling show() again before that animation ends just cancels the pending disposal instead of tearing everything down.
  • Closing resets the window by default — position, size, minimized and fullscreen state all clear, so the next show() starts fresh. Set PiPConfig.resetOnClose: false if you'd rather it remember where it was left.

How it's put together #

Piece What it does
PiPManager The engine — holds all state (child, config, position, size, etc). Use your own instance for more than one independent PiP scope.
PiPController Static facade over the default PiPManager.show(), .hide(), .expand(), .minimize(), .enterFullscreen(), .exitFullscreen(), .moveTo().
PiPFloatingWindow The only widget that touches gestures/animation — rebuilds stay scoped here.
PiPView A thin Stack of your app plus PiPFloatingWindow, for teams who'd rather have it in the widget tree than call show() cold.

None of these know or care what they're displaying — PiPView(child: AnythingAtAll()) just works.

What about OS-level PiP (Android/iOS system PiP)? #

NativePiPController is a separate, optional hook for OS-owned PiP (like Android's enterPictureInPictureMode). No platform channel ships with this package — Android can hand any Activity to system PiP, but iOS only offers it through AVPictureInPictureController, which is tied to a video pipeline and doesn't fit a "shrink whatever's on screen" package. Until a host app registers handlers on NativePiPController.channel, its methods are safe no-ops. For anything short of "hand the whole Activity to Android's OS," use the in-app PiPView engine described above instead.

Example #

Check example/lib/main.dart for a full demo: a floating widget with tap-to-fullscreen (opening a real FullCallScreen with mic/camera/end-call controls) plus drag and snap-to-edge. The rest of the API — expand() / minimize(), moveTo(), custom controls/size — is covered by test/pip_view_widget_test.dart.

Bugs & questions #

Report bugs and ask questions on GitHub Issues.

Maintained by Dashstack Infotech, Surat.

1
likes
150
points
105
downloads

Documentation

API reference

Publisher

verified publisherdashstack.tech

Weekly Downloads

A reusable, production-ready Picture-in-Picture (PiP) floating view engine for Flutter. Turn any widget into a draggable, resizable, edge-snapping floating window with a few lines of code.

Repository (GitHub)
View/report issues

Topics

#picture-in-picture #pip #floating-window #overlay #video

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_pip_view