FSelectMenuTile<T>.builder constructor
FSelectMenuTile<T>.builder ({
- required FSelectGroupController<
T> groupController, - required Widget title,
- int? count,
- FPopoverController? popoverController,
- ScrollController? scrollController,
- FSelectMenuTileStyle? style,
- double? cacheExtent,
- double maxHeight = double.infinity,
- DragStartBehavior dragStartBehavior = DragStartBehavior.start,
- FTileDivider divider = FTileDivider.full,
- Alignment tileAnchor = Alignment.bottomRight,
- Offset shift() = FPortalFollowerShift.flip,
- bool hideOnTapOutside = true,
- bool directionPadding = false,
- bool autoHide = false,
- Widget? label,
- Widget? description,
- Widget errorBuilder() = _errorBuilder,
- String? semanticLabel,
- bool autofocus = false,
- FocusNode? focusNode,
- ValueChanged<
bool> ? onFocusChange, - Widget? prefixIcon,
- Widget? subtitle,
- Widget? details,
- Widget? suffixIcon,
- FormFieldSetter<
Set< ? onSaved,T> > - FormFieldValidator<
Set< ? validator,T> > - Set<
T> ? initialValue, - String? forceErrorText,
- bool enabled = true,
- AutovalidateMode? autovalidateMode,
- String? restorationId,
- Key? key,
Creates a FSelectMenuTile that lazily builds the menu.
The menuTileBuilder
is called for each tile that should be built. FTileData is not visible to
menuTileBuilder
.
- It may return null to signify the end of the group.
- It may be called more than once for the same index.
- It will be called only for indices <=
count
ifcount
is given.
The count
is the number of tiles to build. If null, menuTileBuilder
will be called until it returns null.
Notes
May result in an infinite loop or run out of memory if:
- Placed in a parent widget that does not constrain its size, i.e. Column.
count
is null andmenuTileBuilder
always provides a zero-size widget, i.e. SizedBox(). If possible, provide tiles with non-zero size, return null from builder, or setcount
to non-null.
Implementation
FSelectMenuTile.builder({
required this.groupController,
required this.title,
required FSelectTile<T>? Function(BuildContext, int) menuTileBuilder,
int? count,
this.popoverController,
this.scrollController,
this.style,
this.cacheExtent,
this.maxHeight = double.infinity,
this.dragStartBehavior = DragStartBehavior.start,
this.divider = FTileDivider.full,
this.menuAnchor = Alignment.topRight,
this.tileAnchor = Alignment.bottomRight,
this.shift = FPortalFollowerShift.flip,
this.hideOnTapOutside = true,
this.directionPadding = false,
this.autoHide = false,
this.label,
this.description,
this.errorBuilder = _errorBuilder,
this.semanticLabel,
this.autofocus = false,
this.focusNode,
this.onFocusChange,
this.prefixIcon,
this.subtitle,
this.details,
this.suffixIcon,
super.onSaved,
super.validator,
super.initialValue,
super.forceErrorText,
super.enabled = true,
super.autovalidateMode,
super.restorationId,
super.key,
}) : super(
builder: (field) {
final state = field as _State<T>;
final groupData = FTileGroupData.maybeOf(state.context);
final tileData = FTileData.maybeOf(state.context);
final global = state.context.theme.selectMenuTileStyle;
final labelStyle = style?.labelStyle ?? global.labelStyle;
final menuStyle = style?.menuStyle ?? global.menuStyle;
final tileStyle = style?.tileStyle ?? tileData?.style ?? groupData?.style.tileStyle ?? global.tileStyle;
final (labelState, error) = switch (state.errorText) {
_ when !enabled => (FLabelState.disabled, null),
final text? => (FLabelState.error, errorBuilder(state.context, text)),
null => (FLabelState.enabled, null),
};
Widget tile = FPopover(
// A GlobalObjectKey is used to workaround Flutter not recognizing how widgets move inside the widget tree.
//
// OverlayPortalControllers are tied to a single _OverlayPortalState, and conditional rebuilds introduced
// by FLabel and its internals can cause a new parent to be inserted above FPopover. This leads to the
// entire widget subtree being rebuilt and losing their state. Consequently, the controller is assigned
// another _OverlayPortalState, causing an assertion to be thrown.
//
// See https://stackoverflow.com/a/59410824/4189771
key: GlobalObjectKey(state._controller._popover),
controller: state._controller._popover,
style: menuStyle,
followerAnchor: menuAnchor,
targetAnchor: tileAnchor,
shift: shift,
hideOnTapOutside: hideOnTapOutside,
directionPadding: directionPadding,
autofocus: autofocus,
focusNode: focusNode,
onFocusChange: onFocusChange,
followerBuilder: (context, _, __) => ConstrainedBox(
constraints: BoxConstraints(maxWidth: menuStyle.maxWidth),
child: FSelectTileGroup<T>.builder(
groupController: state._controller,
scrollController: scrollController,
cacheExtent: cacheExtent,
maxHeight: maxHeight,
dragStartBehavior: dragStartBehavior,
style: menuStyle.tileGroupStyle,
semanticLabel: semanticLabel,
divider: divider,
tileBuilder: menuTileBuilder,
count: count,
),
),
target: FTile(
style: tileStyle,
prefixIcon: prefixIcon,
enabled: enabled,
title: title,
subtitle: subtitle,
details: details,
suffixIcon: suffixIcon ?? FIcon(FAssets.icons.chevronsUpDown),
onPress: state._controller._popover.toggle,
),
);
if (groupData == null && tileData == null && (label != null || description != null || error != null)) {
tile = FLabel(
axis: Axis.vertical,
style: labelStyle,
state: labelState,
label: label,
description: description,
error: error,
child: tile,
);
}
return tile;
},
);