mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add Material 3 support for BottomAppBar (#106525)
This commit is contained in:
parent
3894a069b6
commit
0e98194681
@ -20,6 +20,7 @@ import 'dart:io';
|
|||||||
import 'package:gen_defaults/action_chip_template.dart';
|
import 'package:gen_defaults/action_chip_template.dart';
|
||||||
import 'package:gen_defaults/app_bar_template.dart';
|
import 'package:gen_defaults/app_bar_template.dart';
|
||||||
import 'package:gen_defaults/banner_template.dart';
|
import 'package:gen_defaults/banner_template.dart';
|
||||||
|
import 'package:gen_defaults/bottom_app_bar.dart';
|
||||||
import 'package:gen_defaults/bottom_sheet_template.dart';
|
import 'package:gen_defaults/bottom_sheet_template.dart';
|
||||||
import 'package:gen_defaults/button_template.dart';
|
import 'package:gen_defaults/button_template.dart';
|
||||||
import 'package:gen_defaults/card_template.dart';
|
import 'package:gen_defaults/card_template.dart';
|
||||||
@ -119,6 +120,7 @@ Future<void> main(List<String> args) async {
|
|||||||
ActionChipTemplate('Chip', '$materialLib/chip.dart', tokens).updateFile();
|
ActionChipTemplate('Chip', '$materialLib/chip.dart', tokens).updateFile();
|
||||||
ActionChipTemplate('ActionChip', '$materialLib/action_chip.dart', tokens).updateFile();
|
ActionChipTemplate('ActionChip', '$materialLib/action_chip.dart', tokens).updateFile();
|
||||||
AppBarTemplate('AppBar', '$materialLib/app_bar.dart', tokens).updateFile();
|
AppBarTemplate('AppBar', '$materialLib/app_bar.dart', tokens).updateFile();
|
||||||
|
BottomAppBarTemplate('BottomAppBar', '$materialLib/bottom_app_bar.dart', tokens).updateFile();
|
||||||
BannerTemplate('Banner', '$materialLib/banner.dart', tokens).updateFile();
|
BannerTemplate('Banner', '$materialLib/banner.dart', tokens).updateFile();
|
||||||
BottomSheetTemplate('BottomSheet', '$materialLib/bottom_sheet.dart', tokens).updateFile();
|
BottomSheetTemplate('BottomSheet', '$materialLib/bottom_sheet.dart', tokens).updateFile();
|
||||||
ButtonTemplate('md.comp.elevated-button', 'ElevatedButton', '$materialLib/elevated_button.dart', tokens).updateFile();
|
ButtonTemplate('md.comp.elevated-button', 'ElevatedButton', '$materialLib/elevated_button.dart', tokens).updateFile();
|
||||||
|
32
dev/tools/gen_defaults/lib/bottom_app_bar.dart
Normal file
32
dev/tools/gen_defaults/lib/bottom_app_bar.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2014 The Flutter 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 'template.dart';
|
||||||
|
|
||||||
|
class BottomAppBarTemplate extends TokenTemplate {
|
||||||
|
const BottomAppBarTemplate(super.blockName, super.fileName, super.tokens);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String generate() => '''
|
||||||
|
// Generated version ${tokens["version"]}
|
||||||
|
class _${blockName}DefaultsM3 extends BottomAppBarTheme {
|
||||||
|
const _${blockName}DefaultsM3(this.context)
|
||||||
|
: super(
|
||||||
|
elevation: ${elevation('md.comp.bottom-app-bar.container')},
|
||||||
|
height: ${tokens['md.comp.bottom-app-bar.container.height']},
|
||||||
|
);
|
||||||
|
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get color => ${componentColor('md.comp.bottom-app-bar.container')};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get surfaceTintColor => ${componentColor('md.comp.bottom-app-bar.container.surface-tint-layer')};
|
||||||
|
|
||||||
|
@override
|
||||||
|
NotchedShape? get shape => const AutomaticNotchedShape(${shape('md.comp.bottom-app-bar.container')});
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
}
|
193
examples/api/lib/material/bottom_app_bar/bottom_app_bar.2.dart
Normal file
193
examples/api/lib/material/bottom_app_bar/bottom_app_bar.2.dart
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// Flutter code sample for BottomAppBar with Material 3
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const BottomAppBarDemo());
|
||||||
|
}
|
||||||
|
|
||||||
|
class BottomAppBarDemo extends StatefulWidget {
|
||||||
|
const BottomAppBarDemo({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State createState() => _BottomAppBarDemoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
|
||||||
|
static const List<Color> colors = <Color>[
|
||||||
|
Colors.yellow,
|
||||||
|
Colors.orange,
|
||||||
|
Colors.pink,
|
||||||
|
Colors.purple,
|
||||||
|
Colors.cyan,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final List<Widget> items = List<Widget>.generate(
|
||||||
|
colors.length,
|
||||||
|
(int index) => Container(color: colors[index], height: 150.0),
|
||||||
|
).reversed.toList();
|
||||||
|
|
||||||
|
late ScrollController _controller;
|
||||||
|
bool _showFab = true;
|
||||||
|
bool _isElevated = true;
|
||||||
|
bool _isVisible = true;
|
||||||
|
|
||||||
|
FloatingActionButtonLocation get _fabLocation => _isVisible
|
||||||
|
? FloatingActionButtonLocation.endContained
|
||||||
|
: FloatingActionButtonLocation.endFloat;
|
||||||
|
|
||||||
|
void _listen() {
|
||||||
|
final ScrollDirection direction = _controller.position.userScrollDirection;
|
||||||
|
if (direction == ScrollDirection.forward) {
|
||||||
|
_show();
|
||||||
|
} else if (direction == ScrollDirection.reverse) {
|
||||||
|
_hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _show() {
|
||||||
|
if (!_isVisible) {
|
||||||
|
setState(() => _isVisible = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hide() {
|
||||||
|
if (_isVisible) {
|
||||||
|
setState(() => _isVisible = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onShowFabChanged(bool value) {
|
||||||
|
setState(() {
|
||||||
|
_showFab = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onElevatedChanged(bool value) {
|
||||||
|
setState(() {
|
||||||
|
_isElevated = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addNewItem() {
|
||||||
|
setState(() {
|
||||||
|
items.insert(
|
||||||
|
0,
|
||||||
|
Container(color: colors[items.length % 5], height: 150.0),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = ScrollController();
|
||||||
|
_controller.addListener(_listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.removeListener(_listen);
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: true),
|
||||||
|
home: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Bottom App Bar Demo'),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
SwitchListTile(
|
||||||
|
title: const Text('Floating Action Button'),
|
||||||
|
value: _showFab,
|
||||||
|
onChanged: _onShowFabChanged,
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: const Text('Bottom App Bar Elevation'),
|
||||||
|
value: _isElevated,
|
||||||
|
onChanged: _onElevatedChanged,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
controller: _controller,
|
||||||
|
children: items.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: _showFab
|
||||||
|
? FloatingActionButton(
|
||||||
|
onPressed: _addNewItem,
|
||||||
|
tooltip: 'Add New Item',
|
||||||
|
elevation: _isVisible ? 0.0 : null,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
floatingActionButtonLocation: _fabLocation,
|
||||||
|
bottomNavigationBar: _DemoBottomAppBar(isElevated: _isElevated, isVisible: _isVisible),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DemoBottomAppBar extends StatelessWidget {
|
||||||
|
const _DemoBottomAppBar({
|
||||||
|
required this.isElevated,
|
||||||
|
required this.isVisible,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool isElevated;
|
||||||
|
final bool isVisible;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
height: isVisible ? 80.0 : 0,
|
||||||
|
child: BottomAppBar(
|
||||||
|
elevation: isElevated ? null : 0.0,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
tooltip: 'Open popup menu',
|
||||||
|
icon: const Icon(Icons.more_vert),
|
||||||
|
onPressed: () {
|
||||||
|
final SnackBar snackBar = SnackBar(
|
||||||
|
content: const Text('Yay! A SnackBar!'),
|
||||||
|
action: SnackBarAction(
|
||||||
|
label: 'Undo',
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find the ScaffoldMessenger in the widget tree
|
||||||
|
// and use it to show a SnackBar.
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
tooltip: 'Search',
|
||||||
|
icon: const Icon(Icons.search),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
tooltip: 'Favorite',
|
||||||
|
icon: const Icon(Icons.favorite),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -14,9 +14,7 @@ import 'theme.dart';
|
|||||||
// Examples can assume:
|
// Examples can assume:
|
||||||
// late Widget bottomAppBarContents;
|
// late Widget bottomAppBarContents;
|
||||||
|
|
||||||
/// A container that is typically used with [Scaffold.bottomNavigationBar], and
|
/// A container that is typically used with [Scaffold.bottomNavigationBar].
|
||||||
/// can have a notch along the top that makes room for an overlapping
|
|
||||||
/// [FloatingActionButton].
|
|
||||||
///
|
///
|
||||||
/// Typically used with a [Scaffold] and a [FloatingActionButton].
|
/// Typically used with a [Scaffold] and a [FloatingActionButton].
|
||||||
///
|
///
|
||||||
@ -40,6 +38,15 @@ import 'theme.dart';
|
|||||||
/// ** See code in examples/api/lib/material/bottom_app_bar/bottom_app_bar.1.dart **
|
/// ** See code in examples/api/lib/material/bottom_app_bar/bottom_app_bar.1.dart **
|
||||||
/// {@end-tool}
|
/// {@end-tool}
|
||||||
///
|
///
|
||||||
|
/// {@tool dartpad}
|
||||||
|
/// This example shows Material 3 [BottomAppBar] with its expected look and behaviors.
|
||||||
|
///
|
||||||
|
/// This also includes an optional [FloatingActionButton], which illustrates
|
||||||
|
/// the [FloatingActionButtonLocation.endContained].
|
||||||
|
///
|
||||||
|
/// ** See code in examples/api/lib/material/bottom_app_bar/bottom_app_bar.2.dart **
|
||||||
|
/// {@end-tool}
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [NotchedShape] which calculates the notch for a notched [BottomAppBar].
|
/// * [NotchedShape] which calculates the notch for a notched [BottomAppBar].
|
||||||
@ -62,6 +69,8 @@ class BottomAppBar extends StatefulWidget {
|
|||||||
this.clipBehavior = Clip.none,
|
this.clipBehavior = Clip.none,
|
||||||
this.notchMargin = 4.0,
|
this.notchMargin = 4.0,
|
||||||
this.child,
|
this.child,
|
||||||
|
this.surfaceTintColor,
|
||||||
|
this.height,
|
||||||
}) : assert(elevation == null || elevation >= 0.0),
|
}) : assert(elevation == null || elevation >= 0.0),
|
||||||
assert(notchMargin != null),
|
assert(notchMargin != null),
|
||||||
assert(clipBehavior != null);
|
assert(clipBehavior != null);
|
||||||
@ -88,8 +97,8 @@ class BottomAppBar extends StatefulWidget {
|
|||||||
/// value is non-negative.
|
/// value is non-negative.
|
||||||
///
|
///
|
||||||
/// If this property is null then [BottomAppBarTheme.elevation] of
|
/// If this property is null then [BottomAppBarTheme.elevation] of
|
||||||
/// [ThemeData.bottomAppBarTheme] is used. If that's null, the default value
|
/// [ThemeData.bottomAppBarTheme] is used. If that's null and
|
||||||
/// is 8.
|
/// [ThemeData.useMaterial3] is true, than the default value is 3 else is 8.
|
||||||
final double? elevation;
|
final double? elevation;
|
||||||
|
|
||||||
/// The notch that is made for the floating action button.
|
/// The notch that is made for the floating action button.
|
||||||
@ -110,6 +119,23 @@ class BottomAppBar extends StatefulWidget {
|
|||||||
/// Not used if [shape] is null.
|
/// Not used if [shape] is null.
|
||||||
final double notchMargin;
|
final double notchMargin;
|
||||||
|
|
||||||
|
/// The color used as an overlay on [color] to indicate elevation.
|
||||||
|
///
|
||||||
|
/// If this is null, no overlay will be applied. Otherwise the
|
||||||
|
/// color will be composited on top of [color] with an opacity related
|
||||||
|
/// to [elevation] and used to paint the background of the [BottomAppBar].
|
||||||
|
///
|
||||||
|
/// The default is null.
|
||||||
|
///
|
||||||
|
/// See [Material.surfaceTintColor] for more details on how this overlay is applied.
|
||||||
|
final Color? surfaceTintColor;
|
||||||
|
|
||||||
|
/// The double value used to indicate the height of the [BottomAppBar].
|
||||||
|
///
|
||||||
|
/// If this is null, the default value is the minimum in relation to the content,
|
||||||
|
/// unless [ThemeData.useMaterial3] is true, in which case it defaults to 80.0.
|
||||||
|
final double? height;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State createState() => _BottomAppBarState();
|
State createState() => _BottomAppBarState();
|
||||||
}
|
}
|
||||||
@ -117,7 +143,6 @@ class BottomAppBar extends StatefulWidget {
|
|||||||
class _BottomAppBarState extends State<BottomAppBar> {
|
class _BottomAppBarState extends State<BottomAppBar> {
|
||||||
late ValueListenable<ScaffoldGeometry> geometryListenable;
|
late ValueListenable<ScaffoldGeometry> geometryListenable;
|
||||||
final GlobalKey materialKey = GlobalKey();
|
final GlobalKey materialKey = GlobalKey();
|
||||||
static const double _defaultElevation = 8.0;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
@ -127,9 +152,13 @@ class _BottomAppBarState extends State<BottomAppBar> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
final bool isMaterial3 = theme.useMaterial3;
|
||||||
final BottomAppBarTheme babTheme = BottomAppBarTheme.of(context);
|
final BottomAppBarTheme babTheme = BottomAppBarTheme.of(context);
|
||||||
|
final BottomAppBarTheme defaults = isMaterial3 ? _BottomAppBarDefaultsM3(context) : _BottomAppBarDefaultsM2(context);
|
||||||
|
|
||||||
final bool hasFab = Scaffold.of(context).hasFloatingActionButton;
|
final bool hasFab = Scaffold.of(context).hasFloatingActionButton;
|
||||||
final NotchedShape? notchedShape = widget.shape ?? babTheme.shape;
|
final NotchedShape? notchedShape = widget.shape ?? babTheme.shape ?? defaults.shape;
|
||||||
final CustomClipper<Path> clipper = notchedShape != null && hasFab
|
final CustomClipper<Path> clipper = notchedShape != null && hasFab
|
||||||
? _BottomAppBarClipper(
|
? _BottomAppBarClipper(
|
||||||
geometry: geometryListenable,
|
geometry: geometryListenable,
|
||||||
@ -138,20 +167,33 @@ class _BottomAppBarState extends State<BottomAppBar> {
|
|||||||
notchMargin: widget.notchMargin,
|
notchMargin: widget.notchMargin,
|
||||||
)
|
)
|
||||||
: const ShapeBorderClipper(shape: RoundedRectangleBorder());
|
: const ShapeBorderClipper(shape: RoundedRectangleBorder());
|
||||||
final double elevation = widget.elevation ?? babTheme.elevation ?? _defaultElevation;
|
final double elevation = widget.elevation ?? babTheme.elevation ?? defaults.elevation!;
|
||||||
final Color color = widget.color ?? babTheme.color ?? Theme.of(context).bottomAppBarColor;
|
final double? height = widget.height ?? babTheme.height ?? defaults.height;
|
||||||
final Color effectiveColor = ElevationOverlay.applyOverlay(context, color, elevation);
|
final Color color = widget.color ?? babTheme.color ?? defaults.color!;
|
||||||
return PhysicalShape(
|
final Color surfaceTintColor = widget.surfaceTintColor ?? babTheme.surfaceTintColor ?? defaults.surfaceTintColor!;
|
||||||
clipper: clipper,
|
final Color effectiveColor = isMaterial3 ? color : ElevationOverlay.applyOverlay(context, color, elevation);
|
||||||
elevation: elevation,
|
|
||||||
color: effectiveColor,
|
final Widget? child = isMaterial3 ? Padding(
|
||||||
clipBehavior: widget.clipBehavior,
|
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
|
||||||
child: Material(
|
child: widget.child,
|
||||||
key: materialKey,
|
) : widget.child;
|
||||||
type: MaterialType.transparency,
|
|
||||||
child: widget.child == null
|
return SizedBox(
|
||||||
? null
|
height: height,
|
||||||
: SafeArea(child: widget.child!),
|
child: PhysicalShape(
|
||||||
|
clipper: clipper,
|
||||||
|
elevation: elevation,
|
||||||
|
color: effectiveColor,
|
||||||
|
clipBehavior: widget.clipBehavior,
|
||||||
|
child: Material(
|
||||||
|
key: materialKey,
|
||||||
|
type: isMaterial3 ? MaterialType.canvas : MaterialType.transparency,
|
||||||
|
elevation: elevation,
|
||||||
|
surfaceTintColor: surfaceTintColor,
|
||||||
|
child: child == null
|
||||||
|
? null
|
||||||
|
: SafeArea(child: child),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -203,3 +245,49 @@ class _BottomAppBarClipper extends CustomClipper<Path> {
|
|||||||
|| oldClipper.notchMargin != notchMargin;
|
|| oldClipper.notchMargin != notchMargin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _BottomAppBarDefaultsM2 extends BottomAppBarTheme {
|
||||||
|
const _BottomAppBarDefaultsM2(this.context)
|
||||||
|
: super(
|
||||||
|
elevation: 8.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get color => Theme.of(context).bottomAppBarColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get surfaceTintColor => Theme.of(context).colorScheme.surfaceTint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BEGIN GENERATED TOKEN PROPERTIES - BottomAppBar
|
||||||
|
|
||||||
|
// Do not edit by hand. The code between the "BEGIN GENERATED" and
|
||||||
|
// "END GENERATED" comments are generated from data in the Material
|
||||||
|
// Design token database by the script:
|
||||||
|
// dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||||||
|
|
||||||
|
// Token database version: v0_101
|
||||||
|
|
||||||
|
// Generated version v0_101
|
||||||
|
class _BottomAppBarDefaultsM3 extends BottomAppBarTheme {
|
||||||
|
const _BottomAppBarDefaultsM3(this.context)
|
||||||
|
: super(
|
||||||
|
elevation: 3.0,
|
||||||
|
height: 80.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get color => Theme.of(context).colorScheme.surface;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color? get surfaceTintColor => Theme.of(context).colorScheme.surfaceTint;
|
||||||
|
|
||||||
|
@override
|
||||||
|
NotchedShape? get shape => const AutomaticNotchedShape(RoundedRectangleBorder());
|
||||||
|
}
|
||||||
|
|
||||||
|
// END GENERATED TOKEN PROPERTIES - BottomAppBar
|
||||||
|
@ -32,6 +32,8 @@ class BottomAppBarTheme with Diagnosticable {
|
|||||||
this.color,
|
this.color,
|
||||||
this.elevation,
|
this.elevation,
|
||||||
this.shape,
|
this.shape,
|
||||||
|
this.height,
|
||||||
|
this.surfaceTintColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Default value for [BottomAppBar.color].
|
/// Default value for [BottomAppBar.color].
|
||||||
@ -45,17 +47,33 @@ class BottomAppBarTheme with Diagnosticable {
|
|||||||
/// Default value for [BottomAppBar.shape].
|
/// Default value for [BottomAppBar.shape].
|
||||||
final NotchedShape? shape;
|
final NotchedShape? shape;
|
||||||
|
|
||||||
|
/// Default value for [BottomAppBar.height].
|
||||||
|
///
|
||||||
|
/// If null, [BottomAppBar] height will be the minimum on the non material 3.
|
||||||
|
final double? height;
|
||||||
|
|
||||||
|
/// Default value for [BottomAppBar.surfaceTintColor].
|
||||||
|
///
|
||||||
|
/// If null, [BottomAppBar] will not display an overlay color.
|
||||||
|
///
|
||||||
|
/// See [Material.surfaceTintColor] for more details.
|
||||||
|
final Color? surfaceTintColor;
|
||||||
|
|
||||||
/// Creates a copy of this object but with the given fields replaced with the
|
/// Creates a copy of this object but with the given fields replaced with the
|
||||||
/// new values.
|
/// new values.
|
||||||
BottomAppBarTheme copyWith({
|
BottomAppBarTheme copyWith({
|
||||||
Color? color,
|
Color? color,
|
||||||
double? elevation,
|
double? elevation,
|
||||||
NotchedShape? shape,
|
NotchedShape? shape,
|
||||||
|
double? height,
|
||||||
|
Color? surfaceTintColor,
|
||||||
}) {
|
}) {
|
||||||
return BottomAppBarTheme(
|
return BottomAppBarTheme(
|
||||||
color: color ?? this.color,
|
color: color ?? this.color,
|
||||||
elevation: elevation ?? this.elevation,
|
elevation: elevation ?? this.elevation,
|
||||||
shape: shape ?? this.shape,
|
shape: shape ?? this.shape,
|
||||||
|
height: height ?? this.height,
|
||||||
|
surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +93,8 @@ class BottomAppBarTheme with Diagnosticable {
|
|||||||
color: Color.lerp(a?.color, b?.color, t),
|
color: Color.lerp(a?.color, b?.color, t),
|
||||||
elevation: lerpDouble(a?.elevation, b?.elevation, t),
|
elevation: lerpDouble(a?.elevation, b?.elevation, t),
|
||||||
shape: t < 0.5 ? a?.shape : b?.shape,
|
shape: t < 0.5 ? a?.shape : b?.shape,
|
||||||
|
height: lerpDouble(a?.height, b?.height, t),
|
||||||
|
surfaceTintColor: Color.lerp(a?.color, b?.color, t),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +103,8 @@ class BottomAppBarTheme with Diagnosticable {
|
|||||||
color,
|
color,
|
||||||
elevation,
|
elevation,
|
||||||
shape,
|
shape,
|
||||||
|
height,
|
||||||
|
surfaceTintColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -96,7 +118,9 @@ class BottomAppBarTheme with Diagnosticable {
|
|||||||
return other is BottomAppBarTheme
|
return other is BottomAppBarTheme
|
||||||
&& other.color == color
|
&& other.color == color
|
||||||
&& other.elevation == elevation
|
&& other.elevation == elevation
|
||||||
&& other.shape == shape;
|
&& other.shape == shape
|
||||||
|
&& other.height == height
|
||||||
|
&& other.surfaceTintColor == surfaceTintColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -105,5 +129,7 @@ class BottomAppBarTheme with Diagnosticable {
|
|||||||
properties.add(ColorProperty('color', color, defaultValue: null));
|
properties.add(ColorProperty('color', color, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
|
properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<NotchedShape>('shape', shape, defaultValue: null));
|
properties.add(DiagnosticsProperty<NotchedShape>('shape', shape, defaultValue: null));
|
||||||
|
properties.add(DiagnosticsProperty<double>('height', height, defaultValue: null));
|
||||||
|
properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -419,6 +419,16 @@ abstract class FloatingActionButtonLocation {
|
|||||||
/// 
|
/// 
|
||||||
static const FloatingActionButtonLocation miniEndDocked = _MiniEndDockedFabLocation();
|
static const FloatingActionButtonLocation miniEndDocked = _MiniEndDockedFabLocation();
|
||||||
|
|
||||||
|
/// End-aligned [FloatingActionButton], floating over the
|
||||||
|
/// [Scaffold.bottomNavigationBar] so that the floating
|
||||||
|
/// action button lines up with the center of the bottom navigation bar.
|
||||||
|
///
|
||||||
|
/// This is unlikely to be a useful location for apps which has a [BottomNavigationBar]
|
||||||
|
/// or a non material 3 [BottomAppBar].
|
||||||
|
///
|
||||||
|
/// 
|
||||||
|
static const FloatingActionButtonLocation endContained = _EndContainedFabLocation();
|
||||||
|
|
||||||
/// Places the [FloatingActionButton] based on the [Scaffold]'s layout.
|
/// Places the [FloatingActionButton] based on the [Scaffold]'s layout.
|
||||||
///
|
///
|
||||||
/// This uses a [ScaffoldPrelayoutGeometry], which the [Scaffold] constructs
|
/// This uses a [ScaffoldPrelayoutGeometry], which the [Scaffold] constructs
|
||||||
@ -609,6 +619,34 @@ mixin FabDockedOffsetY on StandardFabLocation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mixin for a "contained" floating action button location, such as [FloatingActionButtonLocation.endContained].
|
||||||
|
mixin FabContainedOffsetY on StandardFabLocation {
|
||||||
|
/// Calculates y-offset for [FloatingActionButtonLocation]s floating over the
|
||||||
|
/// [Scaffold.bottomNavigationBar] so that the center of the floating
|
||||||
|
/// action button lines up with the center of the bottom navigation bar.
|
||||||
|
@override
|
||||||
|
double getOffsetY(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
|
||||||
|
final double contentBottom = scaffoldGeometry.contentBottom;
|
||||||
|
final double contentMargin = scaffoldGeometry.scaffoldSize.height - contentBottom;
|
||||||
|
final double bottomViewPadding = scaffoldGeometry.minViewPadding.bottom;
|
||||||
|
final double fabHeight = scaffoldGeometry.floatingActionButtonSize.height;
|
||||||
|
final double bottomMinInset = scaffoldGeometry.minInsets.bottom;
|
||||||
|
|
||||||
|
double safeMargin = 0.0;
|
||||||
|
if (contentMargin > bottomMinInset + fabHeight / 2.0) {
|
||||||
|
// If contentMargin is higher than bottomMinInset enough to display the
|
||||||
|
// FAB without clipping, don't provide a margin
|
||||||
|
safeMargin = 0.0;
|
||||||
|
} else {
|
||||||
|
safeMargin = bottomViewPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
final double fabY = contentBottom - fabHeight / 2.0 - safeMargin;
|
||||||
|
final double maxFabY = scaffoldGeometry.scaffoldSize.height - fabHeight - safeMargin;
|
||||||
|
return math.min(maxFabY, fabY + contentMargin / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Mixin for a "start" floating action button location, such as [FloatingActionButtonLocation.startTop].
|
/// Mixin for a "start" floating action button location, such as [FloatingActionButtonLocation.startTop].
|
||||||
mixin FabStartOffsetX on StandardFabLocation {
|
mixin FabStartOffsetX on StandardFabLocation {
|
||||||
/// Calculates x-offset for start-aligned [FloatingActionButtonLocation]s.
|
/// Calculates x-offset for start-aligned [FloatingActionButtonLocation]s.
|
||||||
@ -798,6 +836,14 @@ class _MiniEndDockedFabLocation extends StandardFabLocation
|
|||||||
String toString() => 'FloatingActionButtonLocation.miniEndDocked';
|
String toString() => 'FloatingActionButtonLocation.miniEndDocked';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _EndContainedFabLocation extends StandardFabLocation
|
||||||
|
with FabEndOffsetX, FabContainedOffsetY {
|
||||||
|
const _EndContainedFabLocation();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'FloatingActionButtonLocation.endContained';
|
||||||
|
}
|
||||||
|
|
||||||
/// Provider of animations to move the [FloatingActionButton] between [FloatingActionButtonLocation]s.
|
/// Provider of animations to move the [FloatingActionButton] between [FloatingActionButtonLocation]s.
|
||||||
///
|
///
|
||||||
/// The [Scaffold] uses [Scaffold.floatingActionButtonAnimator] to define:
|
/// The [Scaffold] uses [Scaffold.floatingActionButtonAnimator] to define:
|
||||||
|
@ -1288,6 +1288,7 @@ class ThemeData with Diagnosticable {
|
|||||||
///
|
///
|
||||||
/// ### Components
|
/// ### Components
|
||||||
/// * Common buttons: [ElevatedButton], [FilledButton], [OutlinedButton], [TextButton], [IconButton]
|
/// * Common buttons: [ElevatedButton], [FilledButton], [OutlinedButton], [TextButton], [IconButton]
|
||||||
|
/// * Bottom app bar: [BottomAppBar]
|
||||||
/// * FAB: [FloatingActionButton]
|
/// * FAB: [FloatingActionButton]
|
||||||
/// * Extended FAB: [FloatingActionButton.extended]
|
/// * Extended FAB: [FloatingActionButton.extended]
|
||||||
/// * Cards: [Card]
|
/// * Cards: [Card]
|
||||||
|
@ -10,92 +10,215 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('BAB theme overrides color', (WidgetTester tester) async {
|
group('Material 2 tests', () {
|
||||||
const Color themedColor = Colors.black87;
|
testWidgets('BAB theme overrides color', (WidgetTester tester) async {
|
||||||
const BottomAppBarTheme theme = BottomAppBarTheme(color: themedColor);
|
const Color themedColor = Colors.black87;
|
||||||
|
const BottomAppBarTheme theme = BottomAppBarTheme(color: themedColor);
|
||||||
|
|
||||||
await tester.pumpWidget(_withTheme(theme));
|
await tester.pumpWidget(_withTheme(theme));
|
||||||
|
|
||||||
final PhysicalShape widget = _getBabRenderObject(tester);
|
final PhysicalShape widget = _getBabRenderObject(tester);
|
||||||
expect(widget.color, themedColor);
|
expect(widget.color, themedColor);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('BAB color - Widget', (WidgetTester tester) async {
|
testWidgets('BAB color - Widget', (WidgetTester tester) async {
|
||||||
const Color themeColor = Colors.white10;
|
const Color themeColor = Colors.white10;
|
||||||
const Color babThemeColor = Colors.black87;
|
const Color babThemeColor = Colors.black87;
|
||||||
const Color babColor = Colors.pink;
|
const Color babColor = Colors.pink;
|
||||||
const BottomAppBarTheme theme = BottomAppBarTheme(color: babThemeColor);
|
const BottomAppBarTheme theme = BottomAppBarTheme(color: babThemeColor);
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
theme: ThemeData(bottomAppBarTheme: theme, bottomAppBarColor: themeColor),
|
theme: ThemeData(
|
||||||
home: const Scaffold(body: BottomAppBar(color: babColor)),
|
bottomAppBarTheme: theme, bottomAppBarColor: themeColor),
|
||||||
));
|
home: const Scaffold(body: BottomAppBar(color: babColor)),
|
||||||
|
));
|
||||||
|
|
||||||
final PhysicalShape widget = _getBabRenderObject(tester);
|
final PhysicalShape widget = _getBabRenderObject(tester);
|
||||||
expect(widget.color, babColor);
|
expect(widget.color, babColor);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('BAB color - BabTheme', (WidgetTester tester) async {
|
testWidgets('BAB color - BabTheme', (WidgetTester tester) async {
|
||||||
const Color themeColor = Colors.white10;
|
const Color themeColor = Colors.white10;
|
||||||
const Color babThemeColor = Colors.black87;
|
const Color babThemeColor = Colors.black87;
|
||||||
const BottomAppBarTheme theme = BottomAppBarTheme(color: babThemeColor);
|
const BottomAppBarTheme theme = BottomAppBarTheme(color: babThemeColor);
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
theme: ThemeData(bottomAppBarTheme: theme, bottomAppBarColor: themeColor),
|
theme: ThemeData(
|
||||||
home: const Scaffold(body: BottomAppBar()),
|
bottomAppBarTheme: theme, bottomAppBarColor: themeColor),
|
||||||
));
|
home: const Scaffold(body: BottomAppBar()),
|
||||||
|
));
|
||||||
|
|
||||||
final PhysicalShape widget = _getBabRenderObject(tester);
|
final PhysicalShape widget = _getBabRenderObject(tester);
|
||||||
expect(widget.color, babThemeColor);
|
expect(widget.color, babThemeColor);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('BAB color - Theme', (WidgetTester tester) async {
|
testWidgets('BAB color - Theme', (WidgetTester tester) async {
|
||||||
const Color themeColor = Colors.white10;
|
const Color themeColor = Colors.white10;
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
theme: ThemeData(bottomAppBarColor: themeColor),
|
theme: ThemeData(bottomAppBarColor: themeColor),
|
||||||
home: const Scaffold(body: BottomAppBar()),
|
home: const Scaffold(body: BottomAppBar()),
|
||||||
));
|
));
|
||||||
|
|
||||||
final PhysicalShape widget = _getBabRenderObject(tester);
|
final PhysicalShape widget = _getBabRenderObject(tester);
|
||||||
expect(widget.color, themeColor);
|
expect(widget.color, themeColor);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('BAB color - Default', (WidgetTester tester) async {
|
testWidgets('BAB color - Default', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
theme: ThemeData(),
|
theme: ThemeData(),
|
||||||
home: const Scaffold(body: BottomAppBar()),
|
home: const Scaffold(body: BottomAppBar()),
|
||||||
));
|
));
|
||||||
|
|
||||||
final PhysicalShape widget = _getBabRenderObject(tester);
|
final PhysicalShape widget = _getBabRenderObject(tester);
|
||||||
|
|
||||||
expect(widget.color, Colors.white);
|
expect(widget.color, Colors.white);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('BAB theme customizes shape', (WidgetTester tester) async {
|
testWidgets('BAB theme customizes shape', (WidgetTester tester) async {
|
||||||
const BottomAppBarTheme theme = BottomAppBarTheme(
|
const BottomAppBarTheme theme = BottomAppBarTheme(
|
||||||
color: Colors.white30,
|
color: Colors.white30,
|
||||||
shape: CircularNotchedRectangle(),
|
shape: CircularNotchedRectangle(),
|
||||||
elevation: 1.0,
|
elevation: 1.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(_withTheme(theme));
|
await tester.pumpWidget(_withTheme(theme));
|
||||||
|
|
||||||
await expectLater(
|
await expectLater(
|
||||||
find.byKey(_painterKey),
|
find.byKey(_painterKey),
|
||||||
matchesGoldenFile('bottom_app_bar_theme.custom_shape.png'),
|
matchesGoldenFile('bottom_app_bar_theme.custom_shape.png'),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'BAB theme does not affect defaults', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(const MaterialApp(
|
||||||
|
home: Scaffold(body: BottomAppBar()),
|
||||||
|
));
|
||||||
|
|
||||||
|
final PhysicalShape widget = _getBabRenderObject(tester);
|
||||||
|
|
||||||
|
expect(widget.color, Colors.white);
|
||||||
|
expect(widget.elevation, equals(8.0));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('BAB theme does not affect defaults', (WidgetTester tester) async {
|
group('Material 3 tests', () {
|
||||||
await tester.pumpWidget(const MaterialApp(
|
Material getBabRenderObject(WidgetTester tester) {
|
||||||
home: Scaffold(body: BottomAppBar()),
|
return tester.widget<Material>(
|
||||||
));
|
find.descendant(
|
||||||
|
of: find.byType(BottomAppBar),
|
||||||
|
matching: find.byType(Material),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final PhysicalShape widget = _getBabRenderObject(tester);
|
testWidgets('BAB theme overrides color - M3', (WidgetTester tester) async {
|
||||||
|
const Color themedColor = Colors.black87;
|
||||||
|
const BottomAppBarTheme theme = BottomAppBarTheme(
|
||||||
|
color: themedColor, elevation: 0);
|
||||||
|
|
||||||
expect(widget.color, Colors.white);
|
await tester.pumpWidget(_withTheme(theme, true));
|
||||||
expect(widget.elevation, equals(8.0));
|
|
||||||
|
final PhysicalShape widget = _getBabRenderObject(tester);
|
||||||
|
expect(widget.color, themedColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('BAB color - Widget - M3', (WidgetTester tester) async {
|
||||||
|
const Color themeColor = Colors.white10;
|
||||||
|
const Color babThemeColor = Colors.black87;
|
||||||
|
const Color babColor = Colors.pink;
|
||||||
|
const BottomAppBarTheme theme = BottomAppBarTheme(color: babThemeColor);
|
||||||
|
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: true,
|
||||||
|
bottomAppBarTheme: theme,
|
||||||
|
bottomAppBarColor: themeColor),
|
||||||
|
home: const Scaffold(body: BottomAppBar(color: babColor)),
|
||||||
|
));
|
||||||
|
|
||||||
|
final PhysicalShape widget = _getBabRenderObject(tester);
|
||||||
|
expect(widget.color, babColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('BAB color - BabTheme - M3', (WidgetTester tester) async {
|
||||||
|
const Color themeColor = Colors.white10;
|
||||||
|
const Color babThemeColor = Colors.black87;
|
||||||
|
const BottomAppBarTheme theme = BottomAppBarTheme(color: babThemeColor);
|
||||||
|
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: true,
|
||||||
|
bottomAppBarTheme: theme,
|
||||||
|
bottomAppBarColor: themeColor),
|
||||||
|
home: const Scaffold(body: BottomAppBar()),
|
||||||
|
));
|
||||||
|
|
||||||
|
final PhysicalShape widget = _getBabRenderObject(tester);
|
||||||
|
expect(widget.color, babThemeColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'BAB theme does not affect defaults - M3', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: true),
|
||||||
|
home: const Scaffold(body: BottomAppBar()),
|
||||||
|
));
|
||||||
|
|
||||||
|
final PhysicalShape widget = _getBabRenderObject(tester);
|
||||||
|
|
||||||
|
expect(widget.color, Colors.white);
|
||||||
|
expect(widget.elevation, equals(3.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('BAB theme overrides surfaceTintColor - M3', (
|
||||||
|
WidgetTester tester) async {
|
||||||
|
const Color babThemeSurfaceTintColor = Colors.black87;
|
||||||
|
const BottomAppBarTheme theme = BottomAppBarTheme(
|
||||||
|
surfaceTintColor: babThemeSurfaceTintColor, elevation: 0);
|
||||||
|
|
||||||
|
await tester.pumpWidget(_withTheme(theme, true));
|
||||||
|
|
||||||
|
final Material widget = getBabRenderObject(tester);
|
||||||
|
expect(widget.surfaceTintColor, babThemeSurfaceTintColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'BAB surfaceTintColor - Widget - M3', (WidgetTester tester) async {
|
||||||
|
const Color themeSurfaceTintColor = Colors.white10;
|
||||||
|
const Color babThemeSurfaceTintColor = Colors.black87;
|
||||||
|
const Color babSurfaceTintColor = Colors.pink;
|
||||||
|
const BottomAppBarTheme theme = BottomAppBarTheme(
|
||||||
|
surfaceTintColor: babThemeSurfaceTintColor);
|
||||||
|
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: true,
|
||||||
|
bottomAppBarTheme: theme,
|
||||||
|
bottomAppBarColor: themeSurfaceTintColor),
|
||||||
|
home: const Scaffold(
|
||||||
|
body: BottomAppBar(surfaceTintColor: babSurfaceTintColor)),
|
||||||
|
));
|
||||||
|
|
||||||
|
final Material widget = getBabRenderObject(tester);
|
||||||
|
expect(widget.surfaceTintColor, babSurfaceTintColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'BAB surfaceTintColor - BabTheme - M3', (WidgetTester tester) async {
|
||||||
|
const Color themeColor = Colors.white10;
|
||||||
|
const Color babThemeColor = Colors.black87;
|
||||||
|
const BottomAppBarTheme theme = BottomAppBarTheme(
|
||||||
|
surfaceTintColor: babThemeColor);
|
||||||
|
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
theme: ThemeData(useMaterial3: true,
|
||||||
|
bottomAppBarTheme: theme,
|
||||||
|
bottomAppBarColor: themeColor),
|
||||||
|
home: const Scaffold(body: BottomAppBar()),
|
||||||
|
));
|
||||||
|
|
||||||
|
final Material widget = getBabRenderObject(tester);
|
||||||
|
expect(widget.surfaceTintColor, babThemeColor);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,9 +233,9 @@ PhysicalShape _getBabRenderObject(WidgetTester tester) {
|
|||||||
|
|
||||||
final Key _painterKey = UniqueKey();
|
final Key _painterKey = UniqueKey();
|
||||||
|
|
||||||
Widget _withTheme(BottomAppBarTheme theme) {
|
Widget _withTheme(BottomAppBarTheme theme, [bool useMaterial3 = false]) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
theme: ThemeData(bottomAppBarTheme: theme),
|
theme: ThemeData(useMaterial3: useMaterial3, bottomAppBarTheme: theme),
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
floatingActionButton: const FloatingActionButton(onPressed: null),
|
floatingActionButton: const FloatingActionButton(onPressed: null),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||||
|
@ -296,6 +296,21 @@ void main() {
|
|||||||
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 572.0));
|
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 572.0));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Contained floating action button locations', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
_buildFrame(
|
||||||
|
location: FloatingActionButtonLocation.endContained,
|
||||||
|
bab: const SizedBox(height: 100.0),
|
||||||
|
viewInsets: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scaffold 800x600, FAB is 56x56, BAB is 800x100, FAB's center is
|
||||||
|
// at the top of the BAB.
|
||||||
|
// Formula: scaffold height - BAB height + FAB height / 2 + BAB top & bottom margins.
|
||||||
|
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 550.0));
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Mini-start-top floating action button location', (WidgetTester tester) async {
|
testWidgets('Mini-start-top floating action button location', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
@ -430,6 +445,12 @@ void main() {
|
|||||||
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX, _dockedOffsetY));
|
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX, _dockedOffsetY));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('endContained', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.endContained));
|
||||||
|
|
||||||
|
expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX, _containedOffsetY));
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('miniStartTop', (WidgetTester tester) async {
|
testWidgets('miniStartTop', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniStartTop));
|
await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniStartTop));
|
||||||
|
|
||||||
@ -1617,6 +1638,7 @@ const double _miniRightOffsetX = _rightOffsetX + kMiniButtonOffsetAdjustment;
|
|||||||
const double _topOffsetY = 56.0;
|
const double _topOffsetY = 56.0;
|
||||||
const double _floatOffsetY = 500.0;
|
const double _floatOffsetY = 500.0;
|
||||||
const double _dockedOffsetY = 544.0;
|
const double _dockedOffsetY = 544.0;
|
||||||
|
const double _containedOffsetY = 544.0 + 56.0 / 2;
|
||||||
const double _miniFloatOffsetY = _floatOffsetY + kMiniButtonOffsetAdjustment;
|
const double _miniFloatOffsetY = _floatOffsetY + kMiniButtonOffsetAdjustment;
|
||||||
|
|
||||||
Widget _singleFabScaffold(
|
Widget _singleFabScaffold(
|
||||||
|
Loading…
Reference in New Issue
Block a user