forked from mirrors/material_3_expressive
Add RailItemButtonM3E widget to enhance customization and animation handling in NavigationRailM3E.
This commit is contained in:
parent
5b27a91894
commit
4940d3ce69
1 changed files with 169 additions and 0 deletions
169
packages/navigation_rail_m3e/lib/src/rail_item_button_m3e.dart
Normal file
169
packages/navigation_rail_m3e/lib/src/rail_item_button_m3e.dart
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:icon_button_m3e/icon_button_m3e.dart';
|
||||
import 'package:navigation_rail_m3e/navigation_rail_m3e.dart';
|
||||
|
||||
/// Internal button used by the NavigationRail item that can look like
|
||||
/// an IconButton (collapsed) or a text button (expanded) without
|
||||
/// switching widget types. This avoids animation hitches when the
|
||||
/// rail animates between collapsed and expanded.
|
||||
class RailItemButtonM3E extends StatelessWidget {
|
||||
const RailItemButtonM3E({
|
||||
super.key,
|
||||
required this.icon,
|
||||
this.selectedIcon,
|
||||
required this.isSelected,
|
||||
required this.onPressed,
|
||||
required this.expanded,
|
||||
required this.labelBehavior,
|
||||
required this.label,
|
||||
this.semanticLabel,
|
||||
this.suppressInk = false,
|
||||
this.badgeCount,
|
||||
});
|
||||
|
||||
final Widget icon;
|
||||
final Widget? selectedIcon;
|
||||
final bool isSelected;
|
||||
final VoidCallback onPressed;
|
||||
final bool expanded;
|
||||
final NavigationRailM3ELabelBehavior labelBehavior;
|
||||
final String label;
|
||||
final String? semanticLabel;
|
||||
final bool suppressInk;
|
||||
final int? badgeCount;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context).extension<NavigationRailM3ETheme>() ??
|
||||
const NavigationRailM3ETheme();
|
||||
final tokens = NavigationRailTokensAdapter(context);
|
||||
|
||||
final double height = expanded ? theme.itemHeight : theme.itemHeight;
|
||||
final bool selected = isSelected;
|
||||
|
||||
// Colors and shape per state.
|
||||
final Color fg =
|
||||
selected ? tokens.activeIconAndLabel : tokens.inactiveIconAndLabel;
|
||||
final Color bg =
|
||||
expanded && selected ? tokens.activeIndicatorColor : Colors.transparent;
|
||||
final ShapeBorder shape =
|
||||
expanded ? tokens.indicatorShapeFull : const RoundedRectangleBorder();
|
||||
|
||||
// Content
|
||||
final Widget effectiveIcon =
|
||||
selected && selectedIcon != null ? selectedIcon! : icon;
|
||||
|
||||
Widget content;
|
||||
if (expanded) {
|
||||
final textExpended = Flexible(
|
||||
child: DefaultTextStyle.merge(
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
child: Text(label, semanticsLabel: semanticLabel ?? label),
|
||||
),
|
||||
);
|
||||
|
||||
content = Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconTheme.merge(
|
||||
data: IconThemeData(color: fg, size: theme.iconSize),
|
||||
child: effectiveIcon,
|
||||
),
|
||||
SizedBox(width: theme.iconLabelGap),
|
||||
textExpended,
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: theme.iconLabelGap),
|
||||
child: RailBadgeM3E(count: badgeCount),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
final textCollapsed = Flexible(
|
||||
child: DefaultTextStyle.merge(
|
||||
style: Theme.of(context).textTheme.labelMedium!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
child: Text(label, semanticsLabel: semanticLabel ?? label),
|
||||
),
|
||||
);
|
||||
|
||||
content = Column(
|
||||
children: [
|
||||
IconButtonM3E(
|
||||
icon: IconTheme.merge(
|
||||
data: IconThemeData(color: fg, size: theme.iconSize),
|
||||
child: effectiveIcon,
|
||||
),
|
||||
width: IconButtonM3EWidth.wide,
|
||||
badgeValue: badgeCount,
|
||||
onPressed: onPressed,
|
||||
variant: isSelected
|
||||
? IconButtonM3EVariant.tonal
|
||||
: IconButtonM3EVariant.standard,
|
||||
shape: IconButtonM3EShapeVariant.round,
|
||||
),
|
||||
if (labelBehavior == NavigationRailM3ELabelBehavior.alwaysShow ||
|
||||
(isSelected == true &&
|
||||
labelBehavior != NavigationRailM3ELabelBehavior.alwaysHide))
|
||||
textCollapsed,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final Material material = Material(
|
||||
color: bg,
|
||||
shape: shape,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onPressed,
|
||||
splashFactory: expanded ? null : NoSplash.splashFactory,
|
||||
hoverColor: expanded ? null : Colors.transparent,
|
||||
highlightColor: expanded ? null : Colors.transparent,
|
||||
child: Padding(
|
||||
// Horizontal padding similar to ButtonM3E sm; for collapsed, none.
|
||||
padding: expanded
|
||||
? EdgeInsetsDirectional.only(
|
||||
start: theme.indicatorLeading,
|
||||
end: theme.indicatorTrailing,
|
||||
)
|
||||
: EdgeInsets.zero,
|
||||
child: Align(
|
||||
alignment: expanded ? Alignment.centerLeft : Alignment.center,
|
||||
child: IconTheme.merge(
|
||||
data: IconThemeData(color: fg, size: theme.iconSize),
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Widget sized = ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: height),
|
||||
child: material,
|
||||
);
|
||||
|
||||
// Tooltip semantics for collapsed state.
|
||||
final Widget withTooltip = expanded
|
||||
? sized
|
||||
: Tooltip(
|
||||
message: semanticLabel ?? label,
|
||||
preferBelow: false,
|
||||
child: sized,
|
||||
);
|
||||
|
||||
return Semantics(
|
||||
button: true,
|
||||
selected: selected,
|
||||
label: expanded ? null : (semanticLabel ?? label),
|
||||
child: withTooltip,
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue