Skip to content

Commit 01b53bd

Browse files
authored
Fix overlap check in bottom app bar's custom clipper (flutter#14813)
1 parent 0b6c193 commit 01b53bd

File tree

2 files changed

+102
-1
lines changed

2 files changed

+102
-1
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class _BottomAppBarClipper extends CustomClipper<Path> {
117117
final Rect button = geometry.value.floatingActionButtonArea
118118
.translate(0.0, geometry.value.bottomNavigationBarTop * -1.0);
119119

120-
if (appBar.overlaps(button)) {
120+
if (!appBar.overlaps(button)) {
121121
return new Path()..addRect(appBar);
122122
}
123123

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2018 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:flutter/material.dart';
7+
import 'package:flutter/rendering.dart';
8+
9+
void main() {
10+
testWidgets('no overlap with floating action button', (WidgetTester tester) async {
11+
await tester.pumpWidget(
12+
new MaterialApp(
13+
home: const Scaffold(
14+
floatingActionButton: const FloatingActionButton(
15+
onPressed: null,
16+
),
17+
bottomNavigationBar: const ShapeListener(const BottomAppBar()),
18+
),
19+
),
20+
);
21+
22+
final ShapeListenerState shapeListenerState = tester.state(find.byType(ShapeListener));
23+
final RenderBox renderBox = tester.renderObject(find.byType(BottomAppBar));
24+
final Path expectedPath = new Path()
25+
..addRect(Offset.zero & renderBox.size);
26+
27+
final Path actualPath = shapeListenerState.cache.value;
28+
expect(
29+
actualPath,
30+
coversSameAreaAs(
31+
expectedPath,
32+
areaToCompare: (Offset.zero & renderBox.size).inflate(5.0),
33+
)
34+
);
35+
});
36+
}
37+
38+
// The bottom app bar clip path computation is only available at paint time.
39+
// In order to examine the notch path we implement this caching painter which
40+
// at paint time looks for for a descendant PhysicalShape and caches the
41+
// clip path it is using.
42+
class ClipCachePainter extends CustomPainter {
43+
ClipCachePainter(this.context);
44+
45+
Path value;
46+
BuildContext context;
47+
48+
@override
49+
void paint(Canvas canvas, Size size) {
50+
final RenderPhysicalShape physicalShape = findPhysicalShapeChild(context);
51+
value = physicalShape.clipper.getClip(size);
52+
}
53+
54+
RenderPhysicalShape findPhysicalShapeChild(BuildContext context) {
55+
RenderPhysicalShape result;
56+
context.visitChildElements((Element e) {
57+
final RenderObject renderObject = e.findRenderObject();
58+
if (renderObject.runtimeType == RenderPhysicalShape) {
59+
assert(result == null);
60+
result = renderObject;
61+
} else {
62+
result = findPhysicalShapeChild(e);
63+
}
64+
});
65+
return result;
66+
}
67+
68+
@override
69+
bool shouldRepaint(ClipCachePainter oldDelegate) {
70+
return true;
71+
}
72+
}
73+
74+
class ShapeListener extends StatefulWidget {
75+
const ShapeListener(this.child);
76+
77+
final Widget child;
78+
79+
@override
80+
State createState() => new ShapeListenerState();
81+
82+
}
83+
84+
class ShapeListenerState extends State<ShapeListener> {
85+
@override
86+
Widget build(BuildContext context) {
87+
return new CustomPaint(
88+
child: widget.child,
89+
painter: cache
90+
);
91+
}
92+
93+
ClipCachePainter cache;
94+
95+
@override
96+
void didChangeDependencies() {
97+
super.didChangeDependencies();
98+
cache = new ClipCachePainter(context);
99+
}
100+
101+
}

0 commit comments

Comments
 (0)