debug_panel 0.1.0
debug_panel: ^0.1.0 copied to clipboard
A reusable draggable floating ball with edge-snap spring animation, badge slots, boundary clamping and debug-only mode. Great for debug bubbles and floating menus.
debug_panel #
A reusable draggable floating ball with edge-snap animation for Flutter.
Drag the ball anywhere, release it and it springs to the nearest left/right edge. Perfect for debug bubbles, floating action menus and customer-service buttons — the interactive shell is generic, while all business content is injected by the caller.
Features #
- 🎯 Draggable — move freely within the boundaries (clamped to screen / container edges).
- 🪀 Edge snap — release and it glides to the nearest left/right edge with a bouncy spring animation.
- 🛡️ Boundary clamping — keep the ball below a navbar and above a tab bar via
topBoundary/bottomBoundary. - 🔘 Badge slots — plug in any badge widget (e.g. a red failure counter or a ⏸ pause icon) at
topRightBadge/bottomLeftBadge. - 🎨 Configurable — size, colors, edge margin, initial position, snap duration via
FloatBallConfig. - 🔌 Zero state-management deps — pure Flutter, based on
ChangeNotifier. No GetX / Provider / Bloc required. - 🧪 Debug-only mode — optional
onlyShowInDebugto hide in release builds.
Getting started #
Add the dependency:
dependencies:
debug_panel: ^0.1.0
Then run flutter pub get.
Usage #
Place DebugFloatBall as a direct child of a Stack:
import 'package:debug_panel/debug_panel.dart';
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool _paused = false;
int _failedCount = 0;
int _cacheCount = 3;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
// ... your page content ...
// 浮球:可拖动、松手吸附左右边缘
DebugFloatBall(
// 暂停态 → 外壳变橙
paused: _paused,
// 球心内容
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('📊', style: TextStyle(fontSize: 16)),
if (_cacheCount > 0)
Text(
'$_cacheCount',
style: const TextStyle(
fontSize: 10,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
// 右上角标(失败红点)
topRightBadge: _failedCount > 0
? Container(
padding: const EdgeInsets.symmetric(
horizontal: 5, vertical: 1),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.white, width: 1.5),
),
child: Text(
_failedCount > 99 ? '99+' : '$_failedCount',
style: const TextStyle(
fontSize: 9,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
)
: null,
// 左下角标(暂停 ⏸)
bottomLeftBadge: _paused
? Container(
width: 18,
height: 18,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.orange,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 1.5),
),
child:
const Icon(Icons.pause, size: 11, color: Colors.white),
)
: null,
// 行为回调
onTap: () => debugPrint('ball tapped'),
onDoubleTap: () => setState(() => _paused = !_paused),
onLongPress: () => setState(() => _failedCount = 0),
// 垂直边界(可选):限制在导航栏下、tabbar 上
topBoundary: MediaQuery.of(context).padding.top +
kToolbarHeight,
bottomBoundary: MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.bottom -
kBottomNavigationBarHeight,
),
],
),
);
}
}
For an external controller (e.g. to show/hide the ball from elsewhere):
final controller = FloatBallController(vsync: this); // 在 State 中创建
DebugFloatBall(controller: controller, ...);
// 其它地方
controller.hide();
controller.show();
API #
DebugFloatBall #
| Parameter | Type | Description |
|---|---|---|
controller |
FloatBallController? |
External controller; if omitted one is created internally. |
paused |
bool |
Switch shell to disabledColor. |
child |
Widget? |
Content inside the ball. |
topRightBadge |
Widget? |
Badge at the top-right corner. |
bottomLeftBadge |
Widget? |
Badge at the bottom-left corner. |
topBoundary |
double? |
Upper drag boundary (default: top + status bar). |
bottomBoundary |
double? |
Lower drag boundary (default: bottom - safe area). |
onTap / onDoubleTap / onLongPress |
VoidCallback? |
Gesture callbacks. |
FloatBallController #
Extends ChangeNotifier. Manages position, visibility and the snap animation.
| Method / Getter | Description |
|---|---|
offset |
Current position (Offset, left/top). |
visible |
Whether the ball is visible. |
isSnapping |
Whether the snap animation is running. |
show() / hide() |
Show or hide the ball. |
setOffset(...) |
Jump to a position (clamped). |
updateOffset(delta, ...) |
Apply a drag delta (clamped). |
snapToEdge(...) |
Snap to the nearest left/right edge. |
FloatBallConfig #
| Field | Default | Description |
|---|---|---|
size |
44.0 |
Ball size (round). |
edgeMargin |
16.0 |
Gap to the left/right edge when snapped. |
initialBottomMargin |
120.0 |
Initial distance from the bottom. |
color |
0xCC4A90D9 |
Shell color. |
disabledColor |
0xCCF5A623 |
Shell color when paused. |
snapDuration |
520ms |
Snap animation duration. |
onlyShowInDebug |
false |
Show only in debug builds. |
Additional information #
The package is a pure Flutter (Dart) library — no native code, no state
management dependency. See the example/ directory for a runnable demo.
Report issues at the package repository.