draggable_button_panel 1.0.0-dev.7
draggable_button_panel: ^1.0.0-dev.7 copied to clipboard
The DraggableButtonPanel is a widget displaying a button with a movable panel of options. When clicked, it animates the panel's appearance. Customizable in color, size, and opacity. Implements Statefu [...]
Draggable Button Panel #
Vertical draggable and dockable panel (left/right) composed of rows of buttons. Each row (PanelButton) can either expand to display horizontal options, or function as a toggle (on/off) if it has no options.
Overview #

Key Features #
- Vertical drag with auto-dock to the left or right edge of the screen.
- Per-button animation: only the clicked row expands (others remain unchanged).
- Options (OptionButton) slide out from behind the PanelButton.
- Toggle mode for rows without options, with single or multiple selection.
- onTogglesChanged event emits the list of active items (index + optional id).
- Contextual rounded corners: parent rounded on the free side; only the first/last PanelButton have rounded corners; for options, only the furthest element is vertically rounded.
- Visual feedback during drag accurately reflects the current state (orientation, colors, expansions, toggles).
Installation #
Add draggable_button_panel to your pubspec.yaml then run flutter pub get.
dependencies:
draggable_button_panel: ^1.0.0-dev.3
Then import the package:
import 'package:draggable_button_panel/draggable_button_panel.dart';
Quick API #
ToggleSelectionMode #
single: only one toggleable button can be active at a time.multiple: several can be active.
OptionButton #
Represents an option that unfolds horizontally.
icon(Icon) requiredlabel(String?) optional (informal)tooltip(String?) tooltip shown on hover (desktop/web) or long press (mobile)onPressed(VoidCallback?)color,backgroundColor(Color?)width(double?) horizontal size (default 50)
PanelButton #
Main row of the panel; two uses:
- with
options: the row expands to showOptionButtons. - without
options+toggleable: true: acts as an on/off button.
Main properties:
icon,label,tooltip,onPressed,color,backgroundColorwidth,height(default 50)options(Listtoggleable(bool, default false)initiallyToggled(bool, default false)id(Object?) free identifier (int/String recommended) used in events.
DraggableButtonPanel #
children(Listwidth(double) square size of a row (also used as default for options)buttonColor(Color) default color for buttonscollapseOpacity(double) opacity of inactive rows (0–1)toggleMode(ToggleSelectionMode)onTogglesChanged(ValueChanged<ListonPositionChanged(ValueChangedonMenuExpand(ValueChangedtop(double) vertical position of the panel (mutable to persist position)left(double) [DEPRECATED] horizontal position is no longer used for layout; side is determined by docking (left/right)
ToggleEntry #
Structure emitted in onTogglesChanged:
index(int): position of the row inchildren.id(Object?): optional identifier provided on thePanelButton.
PanelPosition #
Value object used to read or set the panel position in one go:
isDockedLeft(bool): whether the panel is docked to the left.top(double): vertical offset from the top.
Usage Example #
import 'package:flutter/material.dart';
import 'package:draggable_button_panel/draggable_button_panel.dart';
enum PanelBtnId { todo, add, menu }
class Demo extends StatelessWidget {
const Demo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.deepPurpleAccent,
body: Center(
child: DraggableButtonPanel(
width: 50,
buttonColor: Colors.blue,
collapseOpacity: 0.5,
toggleMode: ToggleSelectionMode.multiple,
onTogglesChanged: (entries) {
// entries: list of ToggleEntry (index + optional id)
debugPrint(entries.toString());
},
children: [
// 1) Row with options that unfold
PanelButton(
id: PanelBtnId.menu,
icon: const Icon(Icons.menu_open_rounded, color: Colors.white),
backgroundColor: Colors.redAccent,
tooltip: 'Menu',
options: const [
OptionButton(icon: Icon(Icons.checklist), tooltip: 'My checklist'),
OptionButton(icon: Icon(Icons.add), tooltip: 'Add'),
],
),
// 2) Toggleable row (no options)
const PanelButton(
id: PanelBtnId.add,
toggleable: true,
initiallyToggled: false,
icon: Icon(Icons.add, color: Colors.white),
backgroundColor: Colors.green,
),
],
),
),
);
}
}
Integration Tips #
- If you want to maintain the position between frequent rebuilds, use a
GlobalKey<DraggableButtonPanelState>to read/write thepanelPositionfrom the current state (or callsetPanelPosition). - The visual width of the panel is fixed (based on the max width of the options) to avoid shifts when a row expands; only the clicked row is animated.
- The rounded corners adjust automatically according to the docking side.
Persisting and Restoring Position #
You can listen to the position via onPositionChanged and reapply it later using the public methods of the state:
class MyPageState extends State<MyPage> {
final panelKey = GlobalKey<DraggableButtonPanelState>();
PanelPosition? savedPosition;
@override
Widget build(BuildContext context) {
return Stack(
children: [
DraggableButtonPanel(
key: panelKey,
onPositionChanged: (pos) {
// Save (e.g. in your state, provider, prefs...)
savedPosition = pos;
},
children: const [ /* ... */ ],
),
Positioned(
bottom: 24, left: 24,
child: FilledButton(
onPressed: () {
final state = panelKey.currentState;
if (state == null) return;
// Restore the last known position/docking
if (savedPosition != null) {
state.panelPosition = savedPosition!;
}
},
child: const Text('Restore position'),
),
),
],
);
}
}
Useful API in the state:
panelOffset-> Current Offset(left, top)isDockedLeft-> bool indicating the sidepanelPosition-> get/set the current PanelPosition (top + docking side)setPanelPosition({double? top, bool? dockLeft, bool clampToScreen = true})-> programmatic positioning (legacy-compatible).
License #
BSD 3-Clause License
Copyright (c) 2023–2025, KSɅRKΞV
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.