bottom_app_bar.dart 4.56 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// 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';
11
import 'theme.dart';
12 13 14 15

// Examples can assume:
// Widget bottomAppBarContents;

16
/// A container that is typically used with [Scaffold.bottomNavigationBar], and
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
/// 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,
48
    this.elevation = 8.0,
49 50
    this.shape,
    this.notchMargin = 4.0,
51 52 53 54 55 56 57 58 59 60 61 62 63 64
    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.
65 66
  ///
  /// When null defaults to [ThemeData.bottomAppBarColor].
67 68 69 70 71 72 73 74
  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;

75
  /// The notch that is made for the floating action button.
76
  ///
77 78 79 80 81
  /// If null the bottom app bar will be rectangular with no notch.
  final NotchedShape shape;

  /// The margin between the [FloatingActionButton] and the [BottomAppBar]'s
  /// notch.
82
  ///
83 84
  /// Not used if [shape] is null.
  final double notchMargin;
85

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
  @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) {
101 102 103 104 105 106
    final CustomClipper<Path> clipper = widget.shape != null
      ? new _BottomAppBarClipper(
        geometry: geometryListenable,
        shape: widget.shape,
        notchMargin: widget.notchMargin,
      )
107
      : const ShapeBorderClipper(shape: const RoundedRectangleBorder());
108
    return new PhysicalShape(
109
      clipper: clipper,
110
      elevation: widget.elevation,
111
      color: widget.color ?? Theme.of(context).bottomAppBarColor,
112 113
      child: new Material(
        type: MaterialType.transparency,
114 115 116
        child: widget.child == null
          ? null
          : new SafeArea(child: widget.child),
117 118 119 120 121 122 123
      ),
    );
  }
}

class _BottomAppBarClipper extends CustomClipper<Path> {
  const _BottomAppBarClipper({
124 125 126
    @required this.geometry,
    @required this.shape,
    @required this.notchMargin,
127
  }) : assert(geometry != null),
128 129
       assert(shape != null),
       assert(notchMargin != null),
130 131 132
       super(reclip: geometry);

  final ValueListenable<ScaffoldGeometry> geometry;
133 134
  final NotchedShape shape;
  final double notchMargin;
135 136 137 138

  @override
  Path getClip(Size size) {
    final Rect appBar = Offset.zero & size;
139
    if (geometry.value.floatingActionButtonArea == null) {
140 141 142 143 144 145 146 147
      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);

148
    return shape.getOuterPath(appBar, button.inflate(notchMargin));
149 150 151
  }

  @override
152
  bool shouldReclip(_BottomAppBarClipper oldClipper) => oldClipper.geometry != geometry;
153
}