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 |
|---|---|
![]() |
![]() |

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 yourchild/fullscreenChildonce the close animation finishes, so any camera/audio stream, timer, or socket it holds gets disposed normally. Callingshow()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. SetPiPConfig.resetOnClose: falseif 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.
Libraries
- flutter_pip_view
- 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:

