Skip to content

Instantly share code, notes, and snippets.

@PlugFox
Last active November 19, 2025 18:06
Show Gist options
  • Select an option

  • Save PlugFox/097c35feb4cc3da7a4bce2d902b3a75b to your computer and use it in GitHub Desktop.

Select an option

Save PlugFox/097c35feb4cc3da7a4bce2d902b3a75b to your computer and use it in GitHub Desktop.
InkyIcon widget
/*
* InkyIcon widget.
* https://gist.github.com/PlugFox/097c35feb4cc3da7a4bce2d902b3a75b
* https://dartpad.dev?id=097c35feb4cc3da7a4bce2d902b3a75b
* Mike Matiunin <[email protected]>, 19 November 2025
*/
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
body: SafeArea(
child: Center(
child: DecoratedBox(
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
color: Colors.grey.shade200,
),
child: Padding(
padding: const EdgeInsets.all(8),
child: InkyIcon(
label: 'Inky Icon',
icon: const Icon(Icons.star),
onTap: () => print('Icon tapped'), // ignore: avoid_print
),
),
),
),
),
),
),
);
/// {@template ink_icon}
/// InkIcon widget.
/// {@endtemplate}
class InkyIcon extends StatefulWidget {
/// {@macro ink_icon}
const InkyIcon({
required this.label,
required this.icon,
required this.onTap,
super.key, // ignore: unused_element_parameter
});
final String label;
final Widget icon;
final VoidCallback onTap;
@override
State<InkyIcon> createState() => _InkyIconState();
}
/// State for widget InkIcon.
class _InkyIconState extends State<InkyIcon> {
MaterialInkController? _materialInkController;
late ThemeData _theme;
late IconThemeData _iconTheme;
late TextDirection _textDirection;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_textDirection = Directionality.of(context);
_theme = Theme.of(context);
_iconTheme = _theme.iconTheme.copyWith(size: 32);
}
void _onTap(BuildContext context, Offset position) {
if (!mounted) return;
widget.onTap();
HapticFeedback.lightImpact().ignore();
if (_materialInkController case MaterialInkController controller) {
if (context.findRenderObject() case RenderBox referenceBox) {
_theme.splashFactory
.create(
controller: controller,
color: _theme.splashColor,
textDirection: _textDirection,
referenceBox: referenceBox,
position: position,
)
.confirm();
}
}
}
@override
Widget build(BuildContext context) => GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (details) => _onTap(context, details.localPosition),
child: Padding(
padding: const EdgeInsets.all(4),
child: SizedBox.square(
dimension: 68,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox.square(
dimension: 42,
child: Material(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
type: MaterialType.card,
color: _theme.colorScheme.primaryContainer,
elevation: 4,
clipBehavior: Clip.antiAlias,
child: IconTheme(
data: _iconTheme,
child: Center(
child: Builder(
builder: (context) {
_materialInkController = Material.maybeOf(context);
return widget.icon;
},
),
),
),
),
),
Text(
widget.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium,
),
],
),
),
),
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment