blob: ad4ad563efb4877bd9d046115a5ff08e80394f98 [file] [log] [blame]
// 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(_BottomAppBarClipper oldClipper) => oldClipper.geometry != geometry;
}