Skip to content

Respect button/inputDecoration's borderRadius when barrierCoversButton is false #368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/dropdown_button2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
- Fix DropdownButtonFormField clips text when large text scale is used [Flutter core].
- Fix DropdownButtonFormField padding when ButtonTheme.alignedDropdown is true [Flutter core].
- Add barrierCoversButton to DropdownButtonFormField2.
- Respect button's borderRadius when barrierCoversButton is false.
- Respect inputDecoration's borderRadius when barrierCoversButton is false.

## 3.0.0-beta.21

Expand Down
41 changes: 39 additions & 2 deletions packages/dropdown_button2/lib/src/dropdown_button2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ class DropdownButton2<T> extends StatefulWidget {
'Only one of valueListenable or multiValueListenable can be used.',
),
_inputDecoration = null,
_isEmpty = false;
_isEmpty = false,
_hasError = false;

const DropdownButton2._formField({
super.key,
Expand Down Expand Up @@ -161,9 +162,11 @@ class DropdownButton2<T> extends StatefulWidget {
required this.openDropdownListenable,
required InputDecoration inputDecoration,
required bool isEmpty,
required bool hasError,
}) : underline = null,
_inputDecoration = inputDecoration,
_isEmpty = isEmpty;
_isEmpty = isEmpty,
_hasError = hasError;

/// The list of items the user can select.
///
Expand Down Expand Up @@ -382,6 +385,8 @@ class DropdownButton2<T> extends StatefulWidget {

final bool _isEmpty;

final bool _hasError;

@override
State<DropdownButton2<T>> createState() => _DropdownButton2State<T>();
}
Expand Down Expand Up @@ -572,6 +577,30 @@ class _DropdownButton2State<T> extends State<DropdownButton2<T>> with WidgetsBin
}
}

Set<WidgetState> _materialState(InputDecoration decoration) => <WidgetState>{
if (!decoration.enabled) WidgetState.disabled,
if (_isFocused) WidgetState.focused,
if (_isHovering) WidgetState.hovered,
if (widget._hasError) WidgetState.error,
};

BorderRadius? _getInputDecorationBorderRadius(InputDecoration decoration) {
InputBorder? border;
if (!decoration.enabled) {
border = widget._hasError ? decoration.errorBorder : decoration.disabledBorder;
} else if (_isFocused) {
border = widget._hasError ? decoration.focusedErrorBorder : decoration.focusedBorder;
} else {
border = widget._hasError ? decoration.errorBorder : decoration.enabledBorder;
}
border ??= WidgetStateProperty.resolveAs(decoration.border, _materialState(decoration));

if (border is OutlineInputBorder) {
return border.borderRadius;
}
return null;
}

EdgeInsetsGeometry _buttonAdditionalHPadding() {
final TextDirection? textDirection = Directionality.maybeOf(context);

Expand Down Expand Up @@ -602,6 +631,9 @@ class _DropdownButton2State<T> extends State<DropdownButton2<T>> with WidgetsBin
_dropdownRoute = _DropdownRoute<T>(
items: items,
buttonRect: _buttonRect,
buttonBorderRadius: widget._inputDecoration != null
? _getInputDecorationBorderRadius(widget._inputDecoration!)
: _getButtonBorderRadius(context),
selectedIndex: _selectedIndex ?? 0,
isNoSelectedItem: _selectedIndex == null,
onChanged: widget.onChanged,
Expand Down Expand Up @@ -1026,6 +1058,10 @@ class DropdownButtonFormField2<T> extends FormField<T> {
: effectiveHint != null || effectiveDisabledHint != null;
final bool isEmpty = !showSelectedItem && !isHintOrDisabledHintAvailable;

final bool hasError = field.hasError ||
effectiveDecoration.errorText != null ||
effectiveDecoration.error != null;

// An unFocusable Focus widget so that this widget can detect if its
// descendants have focus or not.
return Focus(
Expand Down Expand Up @@ -1067,6 +1103,7 @@ class DropdownButtonFormField2<T> extends FormField<T> {
hintText: effectiveDecoration.hintText != null ? '' : null,
),
isEmpty: isEmpty,
hasError: hasError,
),
),
);
Expand Down
54 changes: 39 additions & 15 deletions packages/dropdown_button2/lib/src/dropdown_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
_DropdownRoute({
required this.items,
required this.buttonRect,
required this.buttonBorderRadius,
required this.selectedIndex,
required this.isNoSelectedItem,
required this.onChanged,
Expand All @@ -25,6 +26,7 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {

final List<DropdownItem<T>> items;
final ValueNotifier<Rect?> buttonRect;
final BorderRadius? buttonBorderRadius;
final int selectedIndex;
final bool isNoSelectedItem;
final ValueChanged<T?>? onChanged;
Expand Down Expand Up @@ -89,10 +91,11 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
return barrierCoversButton
? routePage
: _CustomModalBarrier(
animation: animation,
barrierColor: _altBarrierColor,
animation: animation,
barrierCurve: barrierCurve,
buttonRect: rect,
buttonBorderRadius: buttonBorderRadius ?? BorderRadius.zero,
child: routePage,
);
},
Expand Down Expand Up @@ -458,18 +461,20 @@ class _DropdownRouteResult<T> {
/// It's used instead of the route barrier when `barrierCoversButton` is set to false.
class _CustomModalBarrier extends StatefulWidget {
const _CustomModalBarrier({
this.animation,
this.barrierColor,
required this.barrierColor,
required this.animation,
required this.barrierCurve,
required this.buttonRect,
required this.buttonBorderRadius,
required this.child,
this.buttonRect,
});

final Animation<double>? animation;
final Color? barrierColor;
final Animation<double>? animation;
final Curve barrierCurve;
final Rect buttonRect;
final BorderRadius buttonBorderRadius;
final Widget child;
final Rect? buttonRect;

@override
State<_CustomModalBarrier> createState() => _CustomModalBarrierState();
Expand Down Expand Up @@ -503,8 +508,9 @@ class _CustomModalBarrierState extends State<_CustomModalBarrier> {
builder: (BuildContext context, Color? value, Widget? child) {
return CustomPaint(
painter: _DropdownBarrierPainter(
buttonRect: widget.buttonRect,
barrierColor: value,
buttonRect: widget.buttonRect,
buttonBorderRadius: widget.buttonBorderRadius,
pageSize: size,
),
);
Expand All @@ -518,29 +524,47 @@ class _CustomModalBarrierState extends State<_CustomModalBarrier> {

class _DropdownBarrierPainter extends CustomPainter {
const _DropdownBarrierPainter({
this.buttonRect,
this.barrierColor,
required this.barrierColor,
required this.buttonRect,
required this.buttonBorderRadius,
required this.pageSize,
});

final Rect? buttonRect;
final Color? barrierColor;
final Rect buttonRect;
final BorderRadius buttonBorderRadius;
final Size pageSize;

@override
void paint(Canvas canvas, Size size) {
if (barrierColor != null && buttonRect != null) {
final Rect rect =
Rect.fromLTRB(-buttonRect!.left, -buttonRect!.top, pageSize.width, pageSize.height);
if (barrierColor != null) {
final Rect rect = Rect.fromLTRB(
-buttonRect.left,
-buttonRect.top,
pageSize.width,
pageSize.height,
);

canvas.saveLayer(rect, Paint());
canvas.drawRect(rect, Paint()..color = barrierColor!);
canvas.drawRect(buttonRect!, Paint()..blendMode = BlendMode.clear);

final RRect buttonRRect = RRect.fromRectAndCorners(
buttonRect,
topLeft: buttonBorderRadius.topLeft,
topRight: buttonBorderRadius.topRight,
bottomLeft: buttonBorderRadius.bottomLeft,
bottomRight: buttonBorderRadius.bottomRight,
);

canvas.drawRRect(buttonRRect, Paint()..blendMode = BlendMode.clear);
canvas.restore();
}
}

@override
bool shouldRepaint(_DropdownBarrierPainter oldPainter) {
return oldPainter.buttonRect != buttonRect || oldPainter.barrierColor != barrierColor;
return oldPainter.buttonRect != buttonRect ||
oldPainter.barrierColor != barrierColor ||
oldPainter.buttonBorderRadius != buttonBorderRadius;
}
}
Loading