// 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/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'material.dart'; import 'scaffold.dart'; import 'theme.dart'; // Examples can assume: // Widget bottomAppBarContents; /// A container that is typically used with [Scaffold.bottomNavigationBar], and /// can have a notch along the top that makes room for an overlapping /// [FloatingActionButton]. /// /// Typically used with a [Scaffold] and a [FloatingActionButton]. /// /// ## Sample code /// /// ```dart /// new Scaffold( /// bottomNavigationBar: new BottomAppBar( /// color: Colors.white, /// child: bottomAppBarContents, /// ), /// floatingActionButton: new FloatingActionButton(onPressed: null), /// ) /// ``` /// /// See also: /// /// * [ComputeNotch] a function used for creating a notch in a shape. /// * [ScaffoldGeometry.floatingActionBarComputeNotch] the [ComputeNotch] used to /// make a notch for the [FloatingActionButton] /// * [FloatingActionButton] which the [BottomAppBar] makes a notch for. /// * [AppBar] for a toolbar that is shown at the top of the screen. class BottomAppBar extends StatefulWidget { /// Creates a bottom application bar. /// /// The [color] and [elevation] arguments must not be null. const BottomAppBar({ Key key, this.color, this.elevation: 8.0, this.hasNotch: true, this.child, }) : assert(elevation != null), assert(elevation >= 0.0), super(key: key); /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.child} /// /// Typically this the child will be a [Row], with the first child /// being an [IconButton] with the [Icons.menu] icon. final Widget child; /// The bottom app bar's background color. /// /// When null defaults to [ThemeData.bottomAppBarColor]. final Color color; /// The z-coordinate at which to place this bottom app bar. This controls the /// size of the shadow below the bottom app bar. /// /// Defaults to 8, the appropriate elevation for bottom app bars. final double elevation; /// Whether to make a notch in the bottom app bar's shape for the floating /// action button. /// /// When true, the bottom app bar uses /// [ScaffoldGeometry.floatingActionButtonNotch] to make a notch along its /// top edge, where it is overlapped by the /// [ScaffoldGeometry.floatingActionButtonArea]. /// /// When false, the shape of the bottom app bar is a rectangle. final bool hasNotch; @override State createState() => new _BottomAppBarState(); } class _BottomAppBarState extends State<BottomAppBar> { ValueListenable<ScaffoldGeometry> geometryListenable; @override void didChangeDependencies() { super.didChangeDependencies(); geometryListenable = Scaffold.geometryOf(context); } @override Widget build(BuildContext context) { final CustomClipper<Path> clipper = widget.hasNotch ? new _BottomAppBarClipper(geometry: geometryListenable) : const ShapeBorderClipper(shape: const RoundedRectangleBorder()); return new PhysicalShape( clipper: clipper, elevation: widget.elevation, color: widget.color ?? Theme.of(context).bottomAppBarColor, child: new Material( type: MaterialType.transparency, child: widget.child == null ? null : new SafeArea(child: widget.child), ), ); } } class _BottomAppBarClipper extends CustomClipper<Path> { const _BottomAppBarClipper({ @required this.geometry }) : assert(geometry != null), super(reclip: geometry); final ValueListenable<ScaffoldGeometry> geometry; @override Path getClip(Size size) { final Rect appBar = Offset.zero & size; if (geometry.value.floatingActionButtonArea == null || geometry.value.floatingActionButtonNotch == null) { return new Path()..addRect(appBar); } // button is the floating action button's bounding rectangle in the // coordinate system that origins at the appBar's top left corner. final Rect button = geometry.value.floatingActionButtonArea .translate(0.0, geometry.value.bottomNavigationBarTop * -1.0); final ComputeNotch computeNotch = geometry.value.floatingActionButtonNotch; return new Path() ..moveTo(appBar.left, appBar.top) ..addPath( computeNotch( appBar, button, new Offset(appBar.left, appBar.top), new Offset(appBar.right, appBar.top) ), Offset.zero ) ..lineTo(appBar.right, appBar.top) ..lineTo(appBar.right, appBar.bottom) ..lineTo(appBar.left, appBar.bottom) ..close(); } @override bool shouldReclip(CustomClipper<Path> oldClipper) { if (oldClipper.runtimeType != _BottomAppBarClipper) return true; final _BottomAppBarClipper typedOldClipper = oldClipper; return typedOldClipper.geometry != geometry; } }