From 01b53bd014d3de8a51b733305542823b4d823dac Mon Sep 17 00:00:00 2001 From: amirh Date: Thu, 22 Feb 2018 09:20:37 -0800 Subject: [PATCH] Fix overlap check in bottom app bar's custom clipper (#14813) --- .../lib/src/material/bottom_app_bar.dart | 2 +- .../test/material/bottom_app_bar_test.dart | 101 ++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 packages/flutter/test/material/bottom_app_bar_test.dart diff --git a/packages/flutter/lib/src/material/bottom_app_bar.dart b/packages/flutter/lib/src/material/bottom_app_bar.dart index 8bf0dc8062..aee08f4ce1 100644 --- a/packages/flutter/lib/src/material/bottom_app_bar.dart +++ b/packages/flutter/lib/src/material/bottom_app_bar.dart @@ -117,7 +117,7 @@ class _BottomAppBarClipper extends CustomClipper { final Rect button = geometry.value.floatingActionButtonArea .translate(0.0, geometry.value.bottomNavigationBarTop * -1.0); - if (appBar.overlaps(button)) { + if (!appBar.overlaps(button)) { return new Path()..addRect(appBar); } diff --git a/packages/flutter/test/material/bottom_app_bar_test.dart b/packages/flutter/test/material/bottom_app_bar_test.dart new file mode 100644 index 0000000000..31cdba2883 --- /dev/null +++ b/packages/flutter/test/material/bottom_app_bar_test.dart @@ -0,0 +1,101 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +void main() { + testWidgets('no overlap with floating action button', (WidgetTester tester) async { + await tester.pumpWidget( + new MaterialApp( + home: const Scaffold( + floatingActionButton: const FloatingActionButton( + onPressed: null, + ), + bottomNavigationBar: const ShapeListener(const BottomAppBar()), + ), + ), + ); + + final ShapeListenerState shapeListenerState = tester.state(find.byType(ShapeListener)); + final RenderBox renderBox = tester.renderObject(find.byType(BottomAppBar)); + final Path expectedPath = new Path() + ..addRect(Offset.zero & renderBox.size); + + final Path actualPath = shapeListenerState.cache.value; + expect( + actualPath, + coversSameAreaAs( + expectedPath, + areaToCompare: (Offset.zero & renderBox.size).inflate(5.0), + ) + ); + }); +} + +// The bottom app bar clip path computation is only available at paint time. +// In order to examine the notch path we implement this caching painter which +// at paint time looks for for a descendant PhysicalShape and caches the +// clip path it is using. +class ClipCachePainter extends CustomPainter { + ClipCachePainter(this.context); + + Path value; + BuildContext context; + + @override + void paint(Canvas canvas, Size size) { + final RenderPhysicalShape physicalShape = findPhysicalShapeChild(context); + value = physicalShape.clipper.getClip(size); + } + + RenderPhysicalShape findPhysicalShapeChild(BuildContext context) { + RenderPhysicalShape result; + context.visitChildElements((Element e) { + final RenderObject renderObject = e.findRenderObject(); + if (renderObject.runtimeType == RenderPhysicalShape) { + assert(result == null); + result = renderObject; + } else { + result = findPhysicalShapeChild(e); + } + }); + return result; + } + + @override + bool shouldRepaint(ClipCachePainter oldDelegate) { + return true; + } +} + +class ShapeListener extends StatefulWidget { + const ShapeListener(this.child); + + final Widget child; + + @override + State createState() => new ShapeListenerState(); + +} + +class ShapeListenerState extends State { + @override + Widget build(BuildContext context) { + return new CustomPaint( + child: widget.child, + painter: cache + ); + } + + ClipCachePainter cache; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + cache = new ClipCachePainter(context); + } + +}