bottom_app_bar.dart 5.62 KB
Newer Older
1 2 3 4 5 6 7 8
// 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';

9
import 'bottom_app_bar_theme.dart';
10 11
import 'material.dart';
import 'scaffold.dart';
12
import 'theme.dart';
13 14 15 16

// Examples can assume:
// Widget bottomAppBarContents;

17
/// A container that is typically used with [Scaffold.bottomNavigationBar], and
18 19 20 21 22
/// can have a notch along the top that makes room for an overlapping
/// [FloatingActionButton].
///
/// Typically used with a [Scaffold] and a [FloatingActionButton].
///
23
/// {@tool sample}
24
/// ```dart
25 26
/// Scaffold(
///   bottomNavigationBar: BottomAppBar(
27 28 29
///     color: Colors.white,
///     child: bottomAppBarContents,
///   ),
30
///   floatingActionButton: FloatingActionButton(onPressed: null),
31 32
/// )
/// ```
33
/// {@end-tool}
34 35 36 37 38
///
/// See also:
///
///  * [ComputeNotch] a function used for creating a notch in a shape.
///  * [ScaffoldGeometry.floatingActionBarComputeNotch] the [ComputeNotch] used to
39
///    make a notch for the [FloatingActionButton].
40 41 42 43 44
///  * [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.
  ///
45
  /// The [clipBehavior] argument must not be null.
46
  /// Additionally, [elevation] must be non-negative.
47 48 49 50
  ///
  /// If [color], [elevation], or [shape] are null, their [BottomAppBarTheme] values will be used.
  /// If the corresponding [BottomAppBarTheme] property is null, then the default
  /// specified in the property's documentation will be used.
51 52 53
  const BottomAppBar({
    Key key,
    this.color,
54
    this.elevation,
55
    this.shape,
56
    this.clipBehavior = Clip.none,
57
    this.notchMargin = 4.0,
58
    this.child,
59 60 61
  }) : assert(clipBehavior != null),
       assert(elevation == null || elevation >= 0.0),
       assert(notchMargin != null),
62 63 64 65 66 67 68 69 70 71 72
       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.
73
  ///
74 75
  /// If this property is null then [ThemeData.bottomAppBarTheme.color] is used,
  /// if that's null then [ThemeData.bottomAppBarColor] is used.
76 77
  final Color color;

78 79 80 81 82
  /// The z-coordinate at which to place this bottom app bar relative to its
  /// parent.
  ///
  /// This controls the size of the shadow below the bottom app bar. The
  /// value is non-negative.
83
  ///
84 85
  /// If this property is null then [ThemeData.bottomAppBarTheme.elevation] is used,
  /// if that's null, the default value is 8.
86 87
  final double elevation;

88
  /// The notch that is made for the floating action button.
89
  ///
90 91
  /// If this property is null then [ThemeData.bottomAppBarTheme.shape] is used,
  /// if that's null then the shape will be rectangular with no notch.
92 93
  final NotchedShape shape;

94 95 96
  /// {@macro flutter.widgets.Clip}
  final Clip clipBehavior;

97 98
  /// The margin between the [FloatingActionButton] and the [BottomAppBar]'s
  /// notch.
99
  ///
100 101
  /// Not used if [shape] is null.
  final double notchMargin;
102

103
  @override
104
  State createState() => _BottomAppBarState();
105 106 107 108
}

class _BottomAppBarState extends State<BottomAppBar> {
  ValueListenable<ScaffoldGeometry> geometryListenable;
109
  static const double _defaultElevation = 8.0;
110 111 112 113 114 115 116 117 118

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    geometryListenable = Scaffold.geometryOf(context);
  }

  @override
  Widget build(BuildContext context) {
119 120 121
    final BottomAppBarTheme babTheme = BottomAppBarTheme.of(context);
    final NotchedShape notchedShape = widget.shape ?? babTheme.shape;
    final CustomClipper<Path> clipper = notchedShape != null
122
      ? _BottomAppBarClipper(
123
        geometry: geometryListenable,
124
        shape: notchedShape,
125 126
        notchMargin: widget.notchMargin,
      )
127
      : const ShapeBorderClipper(shape: RoundedRectangleBorder());
128
    return PhysicalShape(
129
      clipper: clipper,
130
      elevation: widget.elevation ?? babTheme.elevation ?? _defaultElevation,
131
      color: widget.color ?? babTheme.color ?? Theme.of(context).bottomAppBarColor,
132
      clipBehavior: widget.clipBehavior,
133
      child: Material(
134
        type: MaterialType.transparency,
135 136
        child: widget.child == null
          ? null
137
          : SafeArea(child: widget.child),
138 139 140 141 142 143 144
      ),
    );
  }
}

class _BottomAppBarClipper extends CustomClipper<Path> {
  const _BottomAppBarClipper({
145 146 147
    @required this.geometry,
    @required this.shape,
    @required this.notchMargin,
148
  }) : assert(geometry != null),
149 150
       assert(shape != null),
       assert(notchMargin != null),
151 152 153
       super(reclip: geometry);

  final ValueListenable<ScaffoldGeometry> geometry;
154 155
  final NotchedShape shape;
  final double notchMargin;
156 157 158 159

  @override
  Path getClip(Size size) {
    // button is the floating action button's bounding rectangle in the
160 161 162 163 164 165 166
    // coordinate system whose origin is at the appBar's top left corner,
    // or null if there is no floating action button.
    final Rect button = geometry.value.floatingActionButtonArea?.translate(
      0.0,
      geometry.value.bottomNavigationBarTop * -1.0,
    );
    return shape.getOuterPath(Offset.zero & size, button?.inflate(notchMargin));
167 168 169
  }

  @override
170 171 172 173 174
  bool shouldReclip(_BottomAppBarClipper oldClipper) {
    return oldClipper.geometry != geometry
        || oldClipper.shape != shape
        || oldClipper.notchMargin != notchMargin;
  }
175
}