Closed Prymethron closed 3 months ago
Actually I examined NavigationSheetTransitionObserver and seems like dropdown route also extended from ModalRoute so it tries to apply ForwardTransition which is does not fit. So it might be fixed by changing existing conditional statements or adding new ones.
Hi @Prymethron,
seems like dropdown route also extended from ModalRoute so it tries to apply ForwardTransition which is does not fit.
Furthermore, the current NavigationSheet only supports NavigationSheetRoute and its subclasses (and of course not the dropdown route).
For anyone experiencing these issues, I'm currently using the following as a temporary solution (it uses Overlay widget to display the options):
import 'package:flutter/material.dart';
class CustomDropdown<T> extends StatefulWidget {
/// the child widget for the button, this will be ignored if text is supplied
final Widget child;
/// onChange is called when the selected option is changed.;
/// It will pass back the value and the index of the option.
final void Function(int) onChange;
/// list of DropdownItems
final List<DropdownItem<T>> items;
final DropdownStyle dropdownStyle;
/// dropdownButtonStyles passes styles to OutlineButton.styleFrom()
final DropdownButtonStyle dropdownButtonStyle;
/// dropdown button icon defaults to caret
final Icon? icon;
final bool hideIcon;
/// if true the dropdown icon will as a leading icon, default to false
final bool leadingIcon;
const CustomDropdown({
super.key,
this.hideIcon = false,
required this.child,
required this.items,
this.dropdownStyle = const DropdownStyle(),
this.dropdownButtonStyle = const DropdownButtonStyle(),
this.icon,
this.leadingIcon = false,
required this.onChange,
});
@override
State<CustomDropdown> createState() => _CustomDropdownState();
}
class _CustomDropdownState<T> extends State<CustomDropdown<T>>
with TickerProviderStateMixin {
final LayerLink _layerLink = LayerLink();
final ScrollController _scrollController =
ScrollController(initialScrollOffset: 0);
late OverlayEntry _overlayEntry;
bool _isOpen = false;
int _currentIndex = -1;
late AnimationController _animationController;
late Animation<double> _expandAnimation;
late Animation<double> _rotateAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 200));
_expandAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
_rotateAnimation = Tween(begin: 0.0, end: 0.5).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
));
}
@override
Widget build(BuildContext context) {
var style = widget.dropdownButtonStyle;
// link the overlay to the button
return CompositedTransformTarget(
link: _layerLink,
child: Container(
width: style.width,
height: style.height,
padding: style.padding,
decoration: BoxDecoration(
color: style.backgroundColor,
),
child: InkWell(
onTap: _toggleDropdown,
child: Row(
mainAxisAlignment:
style.mainAxisAlignment ?? MainAxisAlignment.center,
textDirection:
widget.leadingIcon ? TextDirection.rtl : TextDirection.ltr,
mainAxisSize: MainAxisSize.min,
children: [
widget.child,
],
),
),
),
);
}
OverlayEntry _createOverlayEntry() {
// find the size and position of the current widget
RenderBox renderBox = context.findRenderObject()! as RenderBox;
var size = renderBox.size;
var offset = renderBox.localToGlobal(Offset.zero);
var topOffset = offset.dy + size.height + 5;
return OverlayEntry(
// full screen GestureDetector to register when a
// user has clicked away from the dropdown
builder: (context) => GestureDetector(
onTap: () => _toggleDropdown(close: true),
behavior: HitTestBehavior.translucent,
// full screen container to register taps anywhere and close drop down
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Stack(
children: [
Positioned(
left: offset.dx,
top: topOffset,
width: widget.dropdownStyle.width ?? size.width,
child: CompositedTransformFollower(
offset:
widget.dropdownStyle.offset ?? Offset(0, size.height + 5),
link: _layerLink,
showWhenUnlinked: false,
child: Material(
elevation: widget.dropdownStyle.elevation ?? 0,
color: widget.dropdownStyle.color,
shape: widget.dropdownStyle.shape,
child: SizeTransition(
axisAlignment: 1,
sizeFactor: _expandAnimation,
child: ConstrainedBox(
constraints: widget.dropdownStyle.constraints ??
BoxConstraints(
maxHeight: (MediaQuery.of(context).size.height -
topOffset -
15)
.isNegative
? 100
: MediaQuery.of(context).size.height -
topOffset -
15,
),
child: RawScrollbar(
thumbVisibility: true,
thumbColor: widget.dropdownStyle.scrollbarColor ??
Colors.grey,
controller: _scrollController,
child: ListView(
padding:
widget.dropdownStyle.padding ?? EdgeInsets.zero,
shrinkWrap: true,
controller: _scrollController,
children: widget.items.asMap().entries.map((item) {
return InkWell(
onTap: () {
setState(() => _currentIndex = item.key);
widget.onChange(item.key);
_toggleDropdown();
},
child: item.value,
);
}).toList(),
),
),
),
),
),
),
),
],
),
),
),
);
}
void _toggleDropdown({bool close = false}) async {
if (_isOpen || close) {
await _animationController.reverse();
_overlayEntry.remove();
setState(() {
_isOpen = false;
});
} else {
_overlayEntry = _createOverlayEntry();
Overlay.of(context).insert(_overlayEntry);
setState(() => _isOpen = true);
_animationController.forward();
}
}
}
/// DropdownItem is just a wrapper for each child in the dropdown list.\n
/// It holds the value of the item.
class DropdownItem<T> extends StatelessWidget {
final T? value;
final Widget child;
const DropdownItem({super.key, this.value, required this.child});
@override
Widget build(BuildContext context) {
return child;
}
}
class DropdownButtonStyle {
final MainAxisAlignment? mainAxisAlignment;
final ShapeBorder? shape;
final double elevation;
final Color? backgroundColor;
final EdgeInsets? padding;
final BoxConstraints? constraints;
final double? width;
final double? height;
final Color? primaryColor;
const DropdownButtonStyle({
this.mainAxisAlignment,
this.backgroundColor,
this.primaryColor,
this.constraints,
this.height,
this.width,
this.elevation = 0,
this.padding,
this.shape,
});
}
class DropdownStyle {
final double? elevation;
final Color? color;
final EdgeInsets? padding;
final BoxConstraints? constraints;
final Color? scrollbarColor;
/// Add shape and border radius of the dropdown from here
final ShapeBorder? shape;
/// position of the top left of the dropdown relative to the top left of the button
final Offset? offset;
///button width must be set for this to take effect
final double? width;
const DropdownStyle({
this.constraints,
this.offset,
this.width,
this.elevation,
this.shape,
this.color,
this.padding,
this.scrollbarColor,
});
}
Credits to the author(s) here.
I was trying to use some dropdown in navigation sheet but I got errors when I tried to open dropdown menu. These are the errors according to versions:
For version 0.4.2: The following assertion was thrown during performLayout(): The sheet extent and the dimensions values must be finalized during the layout phase. 'package:smooth_sheets/src/foundation/framework.dart': Failed assertion: line 125 pos 7: '_extent.hasPixels'
For version 0.5.3: The following assertion was thrown while notifying listeners for ProxyAnimation: 'package:smooth_sheets/src/navigation/navigation_sheet.dart': Failed assertion: line 138 pos 12: '_localExtentScopeKeyRegistry.containsKey(route)': is not true.
Code: