Skip to content

Commit 4648f54

Browse files
authored
Fix chips onDeleted callback don't show the delete button when disabled (#137685)
fixes [Chips with `onDeleted` callback should show the delete button in the `disabled` state](flutter/flutter#136638) ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @OverRide Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: Example(), ); } } class Example extends StatefulWidget { const Example({super.key}); @OverRide State<Example> createState() => _ExampleState(); } class _ExampleState extends State<Example> { @OverRide Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ RawChip( avatar: const Icon(Icons.favorite_rounded), label: const Text('RawChip'), onSelected: null, isEnabled: false, onDeleted: () {}, ), InputChip( avatar: const Icon(Icons.favorite_rounded), label: const Text('InputChip'), isEnabled: false, onPressed: null, onDeleted: () {}, ), FilterChip( avatar: const Icon(Icons.favorite_rounded), label: const Text('FilterChip'), onSelected: null, onDeleted: () {}, ), ], ), ), ); } } ``` </details> | Before | After | | --------------- | --------------- | | <img src="https://github.com/flutter/flutter/assets/48603081/8bd458de-cfd2-44f0-a0dd-a8298938c61f" /> | <img src="https://github.com/flutter/flutter/assets/48603081/afca0684-b061-416b-b029-5316588c6888" /> |
1 parent 023e5ad commit 4648f54

File tree

4 files changed

+83
-9
lines changed

4 files changed

+83
-9
lines changed

packages/flutter/lib/src/material/chip.dart

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2064,26 +2064,39 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
20642064
}
20652065
}
20662066

2067-
final LayerHandle<OpacityLayer> _childOpacityLayerHandler = LayerHandle<OpacityLayer>();
2067+
final LayerHandle<OpacityLayer> _labelOpacityLayerHandler = LayerHandle<OpacityLayer>();
2068+
final LayerHandle<OpacityLayer> _deleteIconOpacityLayerHandler = LayerHandle<OpacityLayer>();
20682069

2069-
void _paintChild(PaintingContext context, Offset offset, RenderBox? child, bool? isEnabled) {
2070+
void _paintChild(PaintingContext context, Offset offset, RenderBox? child, {required bool isDeleteIcon}) {
20702071
if (child == null) {
2071-
_childOpacityLayerHandler.layer = null;
2072+
_labelOpacityLayerHandler.layer = null;
2073+
_deleteIconOpacityLayerHandler.layer = null;
20722074
return;
20732075
}
20742076
final int disabledColorAlpha = _disabledColor.alpha;
20752077
if (!enableAnimation.isCompleted) {
20762078
if (needsCompositing) {
2077-
_childOpacityLayerHandler.layer = context.pushOpacity(
2079+
_labelOpacityLayerHandler.layer = context.pushOpacity(
20782080
offset,
20792081
disabledColorAlpha,
20802082
(PaintingContext context, Offset offset) {
20812083
context.paintChild(child, _boxParentData(child).offset + offset);
20822084
},
2083-
oldLayer: _childOpacityLayerHandler.layer,
2085+
oldLayer: _labelOpacityLayerHandler.layer,
20842086
);
2087+
if (isDeleteIcon) {
2088+
_deleteIconOpacityLayerHandler.layer = context.pushOpacity(
2089+
offset,
2090+
disabledColorAlpha,
2091+
(PaintingContext context, Offset offset) {
2092+
context.paintChild(child, _boxParentData(child).offset + offset);
2093+
},
2094+
oldLayer: _deleteIconOpacityLayerHandler.layer,
2095+
);
2096+
}
20852097
} else {
2086-
_childOpacityLayerHandler.layer = null;
2098+
_labelOpacityLayerHandler.layer = null;
2099+
_deleteIconOpacityLayerHandler.layer = null;
20872100
final Rect childRect = _boxRect(child).shift(offset);
20882101
context.canvas.saveLayer(childRect.inflate(20.0), Paint()..color = _disabledColor);
20892102
context.paintChild(child, _boxParentData(child).offset + offset);
@@ -2114,7 +2127,8 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
21142127

21152128
@override
21162129
void dispose() {
2117-
_childOpacityLayerHandler.layer = null;
2130+
_labelOpacityLayerHandler.layer = null;
2131+
_deleteIconOpacityLayerHandler.layer = null;
21182132
_avatarOpacityLayerHandler.layer = null;
21192133
super.dispose();
21202134
}
@@ -2123,9 +2137,9 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
21232137
void paint(PaintingContext context, Offset offset) {
21242138
_paintAvatar(context, offset);
21252139
if (deleteIconShowing) {
2126-
_paintChild(context, offset, deleteIcon, isEnabled);
2140+
_paintChild(context, offset, deleteIcon, isDeleteIcon: true);
21272141
}
2128-
_paintChild(context, offset, label, isEnabled);
2142+
_paintChild(context, offset, label, isDeleteIcon: false);
21292143
}
21302144

21312145
// Set this to true to have outlines of the tap targets drawn over

packages/flutter/test/material/chip_test.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
// This file is run as part of a reduced test set in CI on Mac and Windows
6+
// machines.
7+
@Tags(<String>['reduced-test-set'])
8+
library;
9+
510
import 'package:flutter/gestures.dart';
611
import 'package:flutter/material.dart';
712
import 'package:flutter/rendering.dart';
@@ -3653,6 +3658,21 @@ void main() {
36533658
expect(tester.takeException(), isNull);
36543659
});
36553660

3661+
testWidgetsWithLeakTracking('Delete button is visible RawChip is disabled', (WidgetTester tester) async {
3662+
await tester.pumpWidget(
3663+
wrapForChip(
3664+
child: RawChip(
3665+
isEnabled: false,
3666+
label: const Text('Label'),
3667+
onDeleted: () { },
3668+
)
3669+
),
3670+
);
3671+
3672+
// Delete button should be visible.
3673+
expectLater(find.byType(RawChip), matchesGoldenFile('raw_chip.disabled.delete_button.png'));
3674+
});
3675+
36563676
group('Material 2', () {
36573677
// These tests are only relevant for Material 2. Once Material 2
36583678
// support is deprecated and the APIs are removed, these tests

packages/flutter/test/material/filter_chip_test.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
// This file is run as part of a reduced test set in CI on Mac and Windows
6+
// machines.
7+
@Tags(<String>['reduced-test-set'])
8+
library;
9+
510
import 'package:flutter/foundation.dart';
611
import 'package:flutter/material.dart';
712
import 'package:flutter_test/flutter_test.dart';
@@ -919,4 +924,19 @@ void main() {
919924

920925
feedback.dispose();
921926
});
927+
928+
testWidgetsWithLeakTracking('Delete button is visible FilterChip is disabled', (WidgetTester tester) async {
929+
await tester.pumpWidget(
930+
wrapForChip(
931+
child: FilterChip(
932+
label: const Text('Label'),
933+
onSelected: null,
934+
onDeleted: () { },
935+
)
936+
),
937+
);
938+
939+
// Delete button should be visible.
940+
expectLater(find.byType(RawChip), matchesGoldenFile('filter_chip.disabled.delete_button.png'));
941+
});
922942
}

packages/flutter/test/material/input_chip_test.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
// This file is run as part of a reduced test set in CI on Mac and Windows
6+
// machines.
7+
@Tags(<String>['reduced-test-set'])
8+
library;
9+
510
import 'package:flutter/material.dart';
611
import 'package:flutter_test/flutter_test.dart';
712
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
@@ -411,4 +416,19 @@ void main() {
411416

412417
expect(getIconData(tester).color, const Color(0xff00ff00));
413418
});
419+
420+
testWidgetsWithLeakTracking('Delete button is visible InputChip is disabled', (WidgetTester tester) async {
421+
await tester.pumpWidget(
422+
wrapForChip(
423+
child: InputChip(
424+
isEnabled: false,
425+
label: const Text('Label'),
426+
onDeleted: () { },
427+
)
428+
),
429+
);
430+
431+
// Delete button should be visible.
432+
expectLater(find.byType(RawChip), matchesGoldenFile('input_chip.disabled.delete_button.png'));
433+
});
414434
}

0 commit comments

Comments
 (0)