bubble_label 3.0.0
bubble_label: ^3.0.0 copied to clipboard
A small Flutter package that shows a floating bubble label anchored to a child widget, and optional background overlay + simple show/dismiss animations.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:bubble_label/bubble_label.dart';
import 'package:s_toggle/s_toggle.dart';
void main() => runApp(const ExampleApp());
/// Example application used in this package's `example` folder.
///
/// Demonstrates typical usage of the `BubbleLabel` API.
class ExampleApp extends StatefulWidget {
/// Creates the example application.
const ExampleApp({super.key});
@override
State<ExampleApp> createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
/// Whether the bubble overlay should ignore pointer events.
///
/// When true (default), the overlay will ignore pointer events so the
/// underlying widgets remain interactive.
bool shouldIgnorePointer = true;
/// Whether to animate show/dismiss operations in the example app.
bool animate = true;
/// Toggle to enable a background overlay behind the bubble.
bool useOverlay = true;
@override
Widget build(BuildContext context) {
return BubbleLabelController(
shouldIgnorePointer: shouldIgnorePointer,
child: MaterialApp(
title: 'Bubble Label Example',
home: Scaffold(
appBar: AppBar(title: const Text('Bubble Label Example')),
body: Column(
spacing: 45,
children: [
/// Configuration toggles
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
height: 80,
child: Column(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.center,
children: [
/// Toggle to allow/disallow bubble pointer events
Flexible(
child: Row(
spacing: 8,
children: [
const Text('Allow bubble pointer events'),
SToggle(
size: 40,
onColor: Colors.green,
offColor: Colors.red,
value: shouldIgnorePointer == false,
onChange: (val) {
setState(() => shouldIgnorePointer = !val);
},
),
],
),
),
/// Toggle to enable/disable animation
Flexible(
child: Row(
spacing: 8,
children: [
const Text('Animate'),
SToggle(
size: 40,
onColor: Colors.green,
offColor: Colors.red,
value: animate,
onChange: (val) => setState(() => animate = val),
),
],
),
),
/// Toggle to enable/disable overlay
Flexible(
child: Row(
spacing: 8,
children: [
const Text('Use overlay'),
SToggle(
size: 40,
onColor: Colors.green,
offColor: Colors.red,
value: useOverlay,
onChange: (val) =>
setState(() => useOverlay = val),
),
],
),
),
],
),
),
),
/// The main example page with buttons to show bubbles.
Flexible(
child: ExamplePage(
animate: animate,
useOverlay: useOverlay,
shouldIgnorePointer: shouldIgnorePointer,
),
),
/// Dismiss buttons
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 15,
children: [
ElevatedButton(
key: const Key('dismiss-button'),
onPressed: () => BubbleLabel.dismiss(animate: false),
child: const Text('Dismiss'),
),
const SizedBox(width: 12),
ElevatedButton(
key: const Key('dismiss-button-animate'),
onPressed: () => BubbleLabel.dismiss(animate: true),
child: const Text('Dismiss (animated)'),
),
],
),
),
],
),
),
),
);
}
}
/// A simple page with buttons that call `BubbleLabel.show` to display
/// sample bubbles so users can try out the package behavior.
class ExamplePage extends StatefulWidget {
/// Whether to animate show/dismiss operations in this example page.
final bool animate;
/// Whether the example shows a background overlay while the bubble is active.
final bool useOverlay;
/// Whether the background overlay should ignore pointer events.
final bool shouldIgnorePointer;
/// Creates an `ExamplePage` used in the example app. It exposes two
/// configurable options: [animate] and [useOverlay].
const ExamplePage({
super.key,
this.animate = true,
this.useOverlay = true,
this.shouldIgnorePointer = true,
});
@override
State<ExamplePage> createState() => _ExamplePageState();
}
class _ExamplePageState extends State<ExamplePage> {
final noOverlayKey = GlobalKey();
final bubbleKey = GlobalKey();
final longPressKey = GlobalKey();
final bubbleButtonKey = GlobalKey();
final positionOverrideButtonKey = GlobalKey();
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const AlwaysScrollableScrollPhysics(),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
spacing: 12,
children: [
/// A simple button that shows a bubble when tapped.
ElevatedButton(
key: bubbleKey,
onPressed: () {
BubbleLabel.show(
bubbleContent: BubbleLabelContent(
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text('Hello bubble!',
style: TextStyle(color: Colors.white)),
),
bubbleColor: Colors.deepPurpleAccent,
backgroundOverlayLayerOpacity: widget.useOverlay ? 0.3 : 0.0,
),
animate: widget.animate,
anchorKey: bubbleKey,
);
},
child: const Text('show bubble'),
),
/// A simple button that shows a bubble without overlay when tapped.
ElevatedButton(
key: noOverlayKey,
onPressed: () {
final bubbleContent = BubbleLabelContent(
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
'bubble 25px above Anchor widget',
style: TextStyle(color: Colors.white),
),
),
bubbleColor: Colors.green,
backgroundOverlayLayerOpacity: 0.0,
verticalPadding: 25,
);
BubbleLabel.show(
bubbleContent: bubbleContent,
animate: widget.animate,
anchorKey: noOverlayKey,
);
},
child: const Text('Bubble 25px above'),
),
/// A long-press area that shows a bubble when long-pressed.
GestureDetector(
key: longPressKey,
onLongPress: () {
final bubbleContent = BubbleLabelContent(
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text('Long press bubble'),
),
bubbleColor: const Color.fromARGB(255, 239, 246, 35),
backgroundOverlayLayerOpacity: widget.useOverlay ? 0.25 : 0.0,
shouldActivateOnLongPressOnAllPlatforms: true,
dismissOnBackgroundTap: true,
);
BubbleLabel.show(
bubbleContent: bubbleContent,
animate: widget.animate,
anchorKey: longPressKey,
);
},
child: Container(
key: const Key('longpress-container'),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.blueGrey.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blueGrey.shade200),
),
height: 90,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Text(
'Long-press to show bubble',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
),
),
SizedBox(height: 4),
Text(
'Tap on background to dismiss',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
],
),
),
),
/// A long-press area that shows a bubble with a button inside when long-pressed.
GestureDetector(
key: bubbleButtonKey,
onTap: () {
final bubbleContent = BubbleLabelContent(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
debugPrint('Button inside bubble tapped');
},
splashColor: Colors.blue,
highlightColor: Colors.blue,
borderRadius: BorderRadius.circular(4),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.black12),
),
padding: const EdgeInsets.all(6.0),
child: const Text('button'),
),
),
),
),
bubbleColor: Colors.greenAccent,
backgroundOverlayLayerOpacity: widget.useOverlay ? 0.25 : 0.0,
shouldActivateOnLongPressOnAllPlatforms: true,
dismissOnBackgroundTap:
widget.shouldIgnorePointer ? false : true,
);
BubbleLabel.show(
bubbleContent: bubbleContent,
animate: widget.animate,
anchorKey: bubbleButtonKey,
);
},
child: Container(
key: const Key('tap-container-button'),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.blueGrey.shade100,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blueGrey.shade300),
),
height: 90,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Text(
'Tap widget',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
),
),
SizedBox(height: 4),
Text(
'A bubble with a button inside',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
],
),
),
),
/// A simple button that shows a bubble at a custom position when tapped.
ElevatedButton(
key: positionOverrideButtonKey,
onPressed: () {
BubbleLabel.show(
bubbleContent: BubbleLabelContent(
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text('Position override bubble at (400, 150)'),
),
bubbleColor: Colors.tealAccent,
positionOverride: const Offset(400, 150),
backgroundOverlayLayerOpacity: widget.useOverlay ? 0.35 : 0.0,
dismissOnBackgroundTap: true,
),
animate: widget.animate,
);
},
child: const Text(
'Bubble\nOffset(400, 150)',
textAlign: TextAlign.center,
),
),
],
),
);
}
}