mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
[Material] Implement TooltipTheme and Tooltip.textStyle, update Tooltip defaults (#36856)
* Implement TooltipThemeData and TooltipTheme * Add text style property * Updated tooltip default colors for light and dark theme to match Material specification
This commit is contained in:
parent
a37921f107
commit
dee8e4211e
|
@ -116,6 +116,7 @@ export 'src/material/time.dart';
|
|||
export 'src/material/time_picker.dart';
|
||||
export 'src/material/toggleable.dart';
|
||||
export 'src/material/tooltip.dart';
|
||||
export 'src/material/tooltip_theme.dart';
|
||||
export 'src/material/typography.dart';
|
||||
export 'src/material/user_accounts_drawer_header.dart';
|
||||
export 'widgets.dart';
|
||||
|
|
|
@ -27,6 +27,7 @@ import 'slider_theme.dart';
|
|||
import 'snack_bar_theme.dart';
|
||||
import 'tab_bar_theme.dart';
|
||||
import 'text_theme.dart';
|
||||
import 'tooltip_theme.dart';
|
||||
import 'typography.dart';
|
||||
|
||||
export 'package:flutter/services.dart' show Brightness;
|
||||
|
@ -155,6 +156,7 @@ class ThemeData extends Diagnosticable {
|
|||
IconThemeData accentIconTheme,
|
||||
SliderThemeData sliderTheme,
|
||||
TabBarTheme tabBarTheme,
|
||||
TooltipThemeData tooltipTheme,
|
||||
CardTheme cardTheme,
|
||||
ChipThemeData chipTheme,
|
||||
TargetPlatform platform,
|
||||
|
@ -257,6 +259,7 @@ class ThemeData extends Diagnosticable {
|
|||
|
||||
sliderTheme ??= const SliderThemeData();
|
||||
tabBarTheme ??= const TabBarTheme();
|
||||
tooltipTheme ??= const TooltipThemeData();
|
||||
appBarTheme ??= const AppBarTheme();
|
||||
bottomAppBarTheme ??= const BottomAppBarTheme();
|
||||
cardTheme ??= const CardTheme();
|
||||
|
@ -313,6 +316,7 @@ class ThemeData extends Diagnosticable {
|
|||
accentIconTheme: accentIconTheme,
|
||||
sliderTheme: sliderTheme,
|
||||
tabBarTheme: tabBarTheme,
|
||||
tooltipTheme: tooltipTheme,
|
||||
cardTheme: cardTheme,
|
||||
chipTheme: chipTheme,
|
||||
platform: platform,
|
||||
|
@ -383,6 +387,7 @@ class ThemeData extends Diagnosticable {
|
|||
@required this.accentIconTheme,
|
||||
@required this.sliderTheme,
|
||||
@required this.tabBarTheme,
|
||||
@required this.tooltipTheme,
|
||||
@required this.cardTheme,
|
||||
@required this.chipTheme,
|
||||
@required this.platform,
|
||||
|
@ -438,6 +443,7 @@ class ThemeData extends Diagnosticable {
|
|||
assert(accentIconTheme != null),
|
||||
assert(sliderTheme != null),
|
||||
assert(tabBarTheme != null),
|
||||
assert(tooltipTheme != null),
|
||||
assert(cardTheme != null),
|
||||
assert(chipTheme != null),
|
||||
assert(platform != null),
|
||||
|
@ -658,12 +664,17 @@ class ThemeData extends Diagnosticable {
|
|||
/// A theme for customizing the size, shape, and color of the tab bar indicator.
|
||||
final TabBarTheme tabBarTheme;
|
||||
|
||||
/// A theme for customizing the visual properties of [Tooltip]s.
|
||||
///
|
||||
/// This is the value returned from [TooltipTheme.of].
|
||||
final TooltipThemeData tooltipTheme;
|
||||
|
||||
/// The colors and styles used to render [Card].
|
||||
///
|
||||
/// This is the value returned from [CardTheme.of].
|
||||
final CardTheme cardTheme;
|
||||
|
||||
/// The colors and styles used to render [Chip], [
|
||||
/// The colors and styles used to render [Chip]s.
|
||||
///
|
||||
/// This is the value returned from [ChipTheme.of].
|
||||
final ChipThemeData chipTheme;
|
||||
|
@ -811,6 +822,7 @@ class ThemeData extends Diagnosticable {
|
|||
IconThemeData accentIconTheme,
|
||||
SliderThemeData sliderTheme,
|
||||
TabBarTheme tabBarTheme,
|
||||
TooltipThemeData tooltipTheme,
|
||||
CardTheme cardTheme,
|
||||
ChipThemeData chipTheme,
|
||||
TargetPlatform platform,
|
||||
|
@ -870,6 +882,7 @@ class ThemeData extends Diagnosticable {
|
|||
accentIconTheme: accentIconTheme ?? this.accentIconTheme,
|
||||
sliderTheme: sliderTheme ?? this.sliderTheme,
|
||||
tabBarTheme: tabBarTheme ?? this.tabBarTheme,
|
||||
tooltipTheme: tooltipTheme ?? this.tooltipTheme,
|
||||
cardTheme: cardTheme ?? this.cardTheme,
|
||||
chipTheme: chipTheme ?? this.chipTheme,
|
||||
platform: platform ?? this.platform,
|
||||
|
@ -1007,6 +1020,7 @@ class ThemeData extends Diagnosticable {
|
|||
accentIconTheme: IconThemeData.lerp(a.accentIconTheme, b.accentIconTheme, t),
|
||||
sliderTheme: SliderThemeData.lerp(a.sliderTheme, b.sliderTheme, t),
|
||||
tabBarTheme: TabBarTheme.lerp(a.tabBarTheme, b.tabBarTheme, t),
|
||||
tooltipTheme: TooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t),
|
||||
cardTheme: CardTheme.lerp(a.cardTheme, b.cardTheme, t),
|
||||
chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t),
|
||||
platform: t < 0.5 ? a.platform : b.platform,
|
||||
|
@ -1072,6 +1086,7 @@ class ThemeData extends Diagnosticable {
|
|||
(otherData.accentIconTheme == accentIconTheme) &&
|
||||
(otherData.sliderTheme == sliderTheme) &&
|
||||
(otherData.tabBarTheme == tabBarTheme) &&
|
||||
(otherData.tooltipTheme == tooltipTheme) &&
|
||||
(otherData.cardTheme == cardTheme) &&
|
||||
(otherData.chipTheme == chipTheme) &&
|
||||
(otherData.platform == platform) &&
|
||||
|
@ -1136,6 +1151,7 @@ class ThemeData extends Diagnosticable {
|
|||
accentIconTheme,
|
||||
sliderTheme,
|
||||
tabBarTheme,
|
||||
tooltipTheme,
|
||||
cardTheme,
|
||||
chipTheme,
|
||||
platform,
|
||||
|
@ -1198,6 +1214,7 @@ class ThemeData extends Diagnosticable {
|
|||
properties.add(DiagnosticsProperty<IconThemeData>('accentIconTheme', accentIconTheme));
|
||||
properties.add(DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme));
|
||||
properties.add(DiagnosticsProperty<TabBarTheme>('tabBarTheme', tabBarTheme));
|
||||
properties.add(DiagnosticsProperty<TooltipThemeData>('tooltipTheme', tooltipTheme));
|
||||
properties.add(DiagnosticsProperty<CardTheme>('cardTheme', cardTheme));
|
||||
properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme));
|
||||
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize));
|
||||
|
|
|
@ -8,9 +8,11 @@ import 'package:flutter/gestures.dart';
|
|||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'colors.dart';
|
||||
import 'feedback.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
import 'tooltip_theme.dart';
|
||||
|
||||
/// A material design tooltip.
|
||||
///
|
||||
|
@ -32,55 +34,54 @@ import 'theme_data.dart';
|
|||
/// See also:
|
||||
///
|
||||
/// * <https://material.io/design/components/tooltips.html>
|
||||
/// * [TooltipTheme] or [ThemeData.tooltipTheme]
|
||||
class Tooltip extends StatefulWidget {
|
||||
/// Creates a tooltip.
|
||||
///
|
||||
/// By default, tooltips prefer to appear below the [child] widget when the
|
||||
/// user long presses on the widget.
|
||||
/// By default, tooltips should adhere to the
|
||||
/// [Material specification](https://material.io/design/components/tooltips.html#spec).
|
||||
/// If the optional constructor parameters are not defined, the values
|
||||
/// provided by [TooltipTheme.of] will be used if a [TooltipTheme] is present
|
||||
/// or specified in [ThemeData].
|
||||
///
|
||||
/// All of the arguments except [child] and [decoration] must not be null.
|
||||
/// All parameters that are defined in the constructor will
|
||||
/// override the default values _and_ the values in [TooltipTheme.of].
|
||||
const Tooltip({
|
||||
Key key,
|
||||
@required this.message,
|
||||
this.height = _defaultTooltipHeight,
|
||||
this.padding = _defaultPadding,
|
||||
this.verticalOffset = _defaultVerticalOffset,
|
||||
this.preferBelow = true,
|
||||
this.excludeFromSemantics = false,
|
||||
this.height,
|
||||
this.padding,
|
||||
this.verticalOffset,
|
||||
this.preferBelow,
|
||||
this.excludeFromSemantics,
|
||||
this.decoration,
|
||||
this.waitDuration = _defaultWaitDuration,
|
||||
this.showDuration = _defaultShowDuration,
|
||||
this.textStyle,
|
||||
this.waitDuration,
|
||||
this.showDuration,
|
||||
this.child,
|
||||
}) : assert(message != null),
|
||||
assert(height != null),
|
||||
assert(padding != null),
|
||||
assert(verticalOffset != null),
|
||||
assert(preferBelow != null),
|
||||
assert(excludeFromSemantics != null),
|
||||
assert(waitDuration != null),
|
||||
assert(showDuration != null),
|
||||
super(key: key);
|
||||
|
||||
static const Duration _defaultShowDuration = Duration(milliseconds: 1500);
|
||||
static const Duration _defaultWaitDuration = Duration(milliseconds: 0);
|
||||
static const double _defaultTooltipHeight = 32.0;
|
||||
static const double _defaultVerticalOffset = 24.0;
|
||||
static const EdgeInsetsGeometry _defaultPadding = EdgeInsets.symmetric(horizontal: 16.0);
|
||||
|
||||
/// The text to display in the tooltip.
|
||||
final String message;
|
||||
|
||||
/// They height of the tooltip's [child].
|
||||
/// The height of the tooltip's [child].
|
||||
///
|
||||
/// If the [child] is null, then this is the intrinsic height.
|
||||
/// If the [child] is null, then this is the tooltip's intrinsic height.
|
||||
final double height;
|
||||
|
||||
/// The amount of space by which to inset the child.
|
||||
/// The amount of space by which to inset the tooltip's [child].
|
||||
///
|
||||
/// Defaults to 16.0 logical pixels in each direction.
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
/// The vertical gap between the widget and the displayed tooltip.
|
||||
///
|
||||
/// When [preferBelow] is set to true and tooltips have sufficient space to
|
||||
/// display themselves, this property defines how much vertical space
|
||||
/// tooltips will position themselves under their corresponding widgets.
|
||||
/// Otherwise, tooltips will position themselves above their corresponding
|
||||
/// widgets with the given offset.
|
||||
final double verticalOffset;
|
||||
|
||||
/// Whether the tooltip defaults to being displayed below the widget.
|
||||
|
@ -92,6 +93,10 @@ class Tooltip extends StatefulWidget {
|
|||
|
||||
/// Whether the tooltip's [message] should be excluded from the semantics
|
||||
/// tree.
|
||||
///
|
||||
/// Defaults to false. A tooltip will add a [Semantics.label] that is set to
|
||||
/// [Tooltip.message]. Set this property to true if the app is going to
|
||||
/// provide its own custom semantics label.
|
||||
final bool excludeFromSemantics;
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
|
@ -101,18 +106,28 @@ class Tooltip extends StatefulWidget {
|
|||
|
||||
/// Specifies the tooltip's shape and background color.
|
||||
///
|
||||
/// If not specified, defaults to a rounded rectangle with a border radius of
|
||||
/// 4.0, and a color derived from the [ThemeData.textTheme] if the
|
||||
/// [ThemeData.brightness] is dark, and [ThemeData.primaryTextTheme] if not.
|
||||
/// The tooltip shape defaults to a rounded rectangle with a border radius of
|
||||
/// 4.0. Tooltips will also default to an opacity of 90% and with the color
|
||||
/// [Colors.grey[700]] if [ThemeData.brightness] is [Brightness.dark], and
|
||||
/// [Colors.white] if it is [Brightness.light].
|
||||
final Decoration decoration;
|
||||
|
||||
/// The amount of time that a pointer must hover over the widget before it
|
||||
/// will show a tooltip.
|
||||
/// The style to use for the message of the tooltip.
|
||||
///
|
||||
/// Defaults to 0 milliseconds (tooltips show immediately upon hover).
|
||||
/// If null, the message's [TextStyle] will be determined based on
|
||||
/// [ThemeData]. If [ThemeData.brightness] is set to [Brightness.dark],
|
||||
/// [ThemeData.textTheme.body1] will be used with [Colors.white]. Otherwise,
|
||||
/// if [ThemeData.brightness] is set to [Brightness.light],
|
||||
/// [ThemeData.textTheme.body1] will be used with [Colors.black].
|
||||
final TextStyle textStyle;
|
||||
|
||||
/// The length of time that a pointer must hover over a tooltip's widget
|
||||
/// before the tooltip will be shown.
|
||||
///
|
||||
/// Defaults to 0 milliseconds (tooltips are shown immediately upon hover).
|
||||
final Duration waitDuration;
|
||||
|
||||
/// The amount of time that the tooltip will be shown once it has appeared.
|
||||
/// The length of time that the tooltip will be shown once it has appeared.
|
||||
///
|
||||
/// Defaults to 1.5 seconds.
|
||||
final Duration showDuration;
|
||||
|
@ -124,24 +139,40 @@ class Tooltip extends StatefulWidget {
|
|||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(StringProperty('message', message, showName: false));
|
||||
properties.add(DoubleProperty('height', height, defaultValue: _defaultTooltipHeight));
|
||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: _defaultPadding));
|
||||
properties.add(DoubleProperty('vertical offset', verticalOffset, defaultValue: _defaultVerticalOffset));
|
||||
properties.add(FlagProperty('position', value: preferBelow, ifTrue: 'below', ifFalse: 'above', showName: true));
|
||||
properties.add(FlagProperty('semantics', value: excludeFromSemantics, ifTrue: 'excluded', showName: true, defaultValue: false));
|
||||
properties.add(DiagnosticsProperty<Duration>('wait duration', waitDuration, defaultValue: _defaultWaitDuration));
|
||||
properties.add(DiagnosticsProperty<Duration>('show duration', showDuration, defaultValue: _defaultShowDuration));
|
||||
properties.add(DoubleProperty('height', height, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
|
||||
properties.add(DoubleProperty('vertical offset', verticalOffset, defaultValue: null));
|
||||
properties.add(FlagProperty('position', value: preferBelow, ifTrue: 'below', ifFalse: 'above', showName: true, defaultValue: null));
|
||||
properties.add(FlagProperty('semantics', value: excludeFromSemantics, ifTrue: 'excluded', showName: true, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Duration>('wait duration', waitDuration, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Duration>('show duration', showDuration, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
|
||||
static const double _defaultTooltipHeight = 32.0;
|
||||
static const double _defaultVerticalOffset = 24.0;
|
||||
static const bool _defaultPreferBelow = true;
|
||||
static const EdgeInsetsGeometry _defaultPadding = EdgeInsets.symmetric(horizontal: 16.0);
|
||||
static const Duration _fadeInDuration = Duration(milliseconds: 150);
|
||||
static const Duration _fadeOutDuration = Duration(milliseconds: 75);
|
||||
static const Duration _defaultShowDuration = Duration(milliseconds: 1500);
|
||||
static const Duration _defaultWaitDuration = Duration(milliseconds: 0);
|
||||
static const bool _defaultExcludeFromSemantics = false;
|
||||
|
||||
double height;
|
||||
EdgeInsetsGeometry padding;
|
||||
Decoration decoration;
|
||||
TextStyle textStyle;
|
||||
double verticalOffset;
|
||||
bool preferBelow;
|
||||
bool excludeFromSemantics;
|
||||
AnimationController _controller;
|
||||
OverlayEntry _entry;
|
||||
Timer _hideTimer;
|
||||
Timer _showTimer;
|
||||
Duration showDuration;
|
||||
Duration waitDuration;
|
||||
bool _mouseIsConnected;
|
||||
bool _longPressActivated = false;
|
||||
|
||||
|
@ -190,7 +221,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
|
|||
}
|
||||
if (_longPressActivated) {
|
||||
// Tool tips activated by long press should stay around for 1.5s.
|
||||
_hideTimer ??= Timer(widget.showDuration, _controller.reverse);
|
||||
_hideTimer ??= Timer(showDuration, _controller.reverse);
|
||||
} else {
|
||||
// Tool tips activated by hover should disappear as soon as the mouse
|
||||
// leaves the control.
|
||||
|
@ -206,7 +237,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
|
|||
ensureTooltipVisible();
|
||||
return;
|
||||
}
|
||||
_showTimer ??= Timer(widget.waitDuration, ensureTooltipVisible);
|
||||
_showTimer ??= Timer(waitDuration, ensureTooltipVisible);
|
||||
}
|
||||
|
||||
/// Shows the tooltip if it is not already visible.
|
||||
|
@ -236,16 +267,17 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
|
|||
// rebuilds.
|
||||
final Widget overlay = _TooltipOverlay(
|
||||
message: widget.message,
|
||||
height: widget.height,
|
||||
padding: widget.padding,
|
||||
decoration: widget.decoration,
|
||||
height: height,
|
||||
padding: padding,
|
||||
decoration: decoration,
|
||||
textStyle: textStyle,
|
||||
animation: CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
),
|
||||
target: target,
|
||||
verticalOffset: widget.verticalOffset,
|
||||
preferBelow: widget.preferBelow,
|
||||
verticalOffset: verticalOffset,
|
||||
preferBelow: preferBelow,
|
||||
);
|
||||
_entry = OverlayEntry(builder: (BuildContext context) => overlay);
|
||||
Overlay.of(context, debugRequiredFor: widget).insert(_entry);
|
||||
|
@ -300,12 +332,44 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(Overlay.of(context, debugRequiredFor: widget) != null);
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final TooltipThemeData tooltipTheme = TooltipTheme.of(context);
|
||||
TextStyle defaultTextStyle;
|
||||
BoxDecoration defaultDecoration;
|
||||
if (theme.brightness == Brightness.dark) {
|
||||
defaultTextStyle = theme.textTheme.body1.copyWith(
|
||||
color: Colors.black,
|
||||
);
|
||||
defaultDecoration = BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
);
|
||||
} else {
|
||||
defaultTextStyle = theme.textTheme.body1.copyWith(
|
||||
color: Colors.white,
|
||||
);
|
||||
defaultDecoration = BoxDecoration(
|
||||
color: Colors.grey[700].withOpacity(0.9),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
);
|
||||
}
|
||||
|
||||
height = widget.height ?? tooltipTheme.height ?? _defaultTooltipHeight;
|
||||
padding = widget.padding ?? tooltipTheme.padding ?? _defaultPadding;
|
||||
verticalOffset = widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset;
|
||||
preferBelow = widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow;
|
||||
excludeFromSemantics = widget.excludeFromSemantics ?? tooltipTheme.excludeFromSemantics ?? _defaultExcludeFromSemantics;
|
||||
decoration = widget.decoration ?? tooltipTheme.decoration ?? defaultDecoration;
|
||||
textStyle = widget.textStyle ?? tooltipTheme.textStyle ?? defaultTextStyle;
|
||||
waitDuration = widget.waitDuration ?? tooltipTheme.waitDuration ?? _defaultWaitDuration;
|
||||
showDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultShowDuration;
|
||||
|
||||
Widget result = GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onLongPress: _handleLongPress,
|
||||
excludeFromSemantics: true,
|
||||
child: Semantics(
|
||||
label: widget.excludeFromSemantics ? null : widget.message,
|
||||
label: excludeFromSemantics ? null : widget.message,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
|
@ -345,7 +409,7 @@ class _TooltipPositionDelegate extends SingleChildLayoutDelegate {
|
|||
/// tooltip.
|
||||
final double verticalOffset;
|
||||
|
||||
/// Whether the tooltip defaults to being displayed below the widget.
|
||||
/// Whether the tooltip is displayed below its widget by default.
|
||||
///
|
||||
/// If there is insufficient space to display the tooltip in the preferred
|
||||
/// direction, the tooltip will be displayed in the opposite direction.
|
||||
|
@ -380,6 +444,7 @@ class _TooltipOverlay extends StatelessWidget {
|
|||
this.height,
|
||||
this.padding,
|
||||
this.decoration,
|
||||
this.textStyle,
|
||||
this.animation,
|
||||
this.target,
|
||||
this.verticalOffset,
|
||||
|
@ -390,6 +455,7 @@ class _TooltipOverlay extends StatelessWidget {
|
|||
final double height;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final Decoration decoration;
|
||||
final TextStyle textStyle;
|
||||
final Animation<double> animation;
|
||||
final Offset target;
|
||||
final double verticalOffset;
|
||||
|
@ -397,12 +463,6 @@ class _TooltipOverlay extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final ThemeData tooltipTheme = ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
textTheme: theme.brightness == Brightness.dark ? theme.textTheme : theme.primaryTextTheme,
|
||||
platform: theme.platform,
|
||||
);
|
||||
return Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: CustomSingleChildLayout(
|
||||
|
@ -416,15 +476,15 @@ class _TooltipOverlay extends StatelessWidget {
|
|||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: height),
|
||||
child: Container(
|
||||
decoration: decoration ?? BoxDecoration(
|
||||
color: tooltipTheme.backgroundColor.withOpacity(0.9),
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
decoration: decoration,
|
||||
padding: padding,
|
||||
child: Center(
|
||||
widthFactor: 1.0,
|
||||
heightFactor: 1.0,
|
||||
child: Text(message, style: tooltipTheme.textTheme.body1),
|
||||
child: Text(
|
||||
message,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
251
packages/flutter/lib/src/material/tooltip_theme.dart
Normal file
251
packages/flutter/lib/src/material/tooltip_theme.dart
Normal file
|
@ -0,0 +1,251 @@
|
|||
// Copyright 2019 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 'dart:ui' show lerpDouble;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'theme.dart';
|
||||
|
||||
/// Defines the visual properties of [Tooltip] widgets.
|
||||
///
|
||||
/// Used by [TooltipTheme] to control the visual properties of tooltips in a
|
||||
/// widget subtree.
|
||||
///
|
||||
/// To obtain this configuration, use [TooltipTheme.of] to access the closest
|
||||
/// ancestor [TooltipTheme] of the current [BuildContext].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [TooltipTheme], an [InheritedWidget] that propagates the theme down its
|
||||
/// subtree.
|
||||
/// * [TooltipThemeData], which describes the actual configuration of a
|
||||
/// tooltip theme.
|
||||
class TooltipThemeData extends Diagnosticable {
|
||||
/// Creates the set of properties used to configure [Tooltip]s.
|
||||
const TooltipThemeData({
|
||||
this.height,
|
||||
this.padding,
|
||||
this.verticalOffset,
|
||||
this.preferBelow,
|
||||
this.excludeFromSemantics,
|
||||
this.decoration,
|
||||
this.textStyle,
|
||||
this.waitDuration,
|
||||
this.showDuration,
|
||||
});
|
||||
|
||||
/// The height of [Tooltip.child].
|
||||
final double height;
|
||||
|
||||
/// If provided, the amount of space by which to inset [Tooltip.child].
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
/// The vertical gap between the widget and the displayed tooltip.
|
||||
///
|
||||
/// When [preferBelow] is set to true and tooltips have sufficient space to
|
||||
/// display themselves, this property defines how much vertical space
|
||||
/// tooltips will position themselves under their corresponding widgets.
|
||||
/// Otherwise, tooltips will position themselves above their corresponding
|
||||
/// widgets with the given offset.
|
||||
final double verticalOffset;
|
||||
|
||||
/// Whether the tooltip is displayed below its widget by default.
|
||||
///
|
||||
/// If there is insufficient space to display the tooltip in the preferred
|
||||
/// direction, the tooltip will be displayed in the opposite direction.
|
||||
final bool preferBelow;
|
||||
|
||||
/// Whether the tooltip's [message] should be excluded from the semantics
|
||||
/// tree.
|
||||
///
|
||||
/// By default, [Tooltip]s will add a [Semantics.label] that is set to
|
||||
/// [Tooltip.message]. Set this property to true if the app is going to
|
||||
/// provide its own custom semantics label.
|
||||
final bool excludeFromSemantics;
|
||||
|
||||
/// The [Tooltip]'s shape and background color.
|
||||
final Decoration decoration;
|
||||
|
||||
/// The style to use for the message of [Tooltip]s.
|
||||
final TextStyle textStyle;
|
||||
|
||||
/// The length of time that a pointer must hover over a tooltip's widget
|
||||
/// before the tooltip will be shown.
|
||||
final Duration waitDuration;
|
||||
|
||||
/// The length of time that the tooltip will be shown once it has appeared.
|
||||
final Duration showDuration;
|
||||
|
||||
/// Creates a copy of this object but with the given fields replaced with the
|
||||
/// new values.
|
||||
TooltipThemeData copyWith({
|
||||
double height,
|
||||
EdgeInsetsGeometry padding,
|
||||
double verticalOffset,
|
||||
bool preferBelow,
|
||||
bool excludeFromSemantics,
|
||||
Decoration decoration,
|
||||
TextStyle textStyle,
|
||||
Duration waitDuration,
|
||||
Duration showDuration,
|
||||
}) {
|
||||
return TooltipThemeData(
|
||||
height: height ?? this.height,
|
||||
padding: padding ?? this.padding,
|
||||
verticalOffset: verticalOffset ?? this.verticalOffset,
|
||||
preferBelow: preferBelow ?? this.preferBelow,
|
||||
excludeFromSemantics: excludeFromSemantics ?? this.excludeFromSemantics,
|
||||
decoration: decoration ?? this.decoration,
|
||||
textStyle: textStyle ?? this.textStyle,
|
||||
waitDuration: waitDuration ?? this.waitDuration,
|
||||
showDuration: showDuration ?? this.showDuration,
|
||||
);
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two tooltip themes.
|
||||
///
|
||||
/// If both arguments are null, then null is returned.
|
||||
///
|
||||
/// {@macro dart.ui.shadow.lerp}
|
||||
static TooltipThemeData lerp(TooltipThemeData a, TooltipThemeData b, double t) {
|
||||
if (a == null && b == null)
|
||||
return null;
|
||||
assert(t != null);
|
||||
return TooltipThemeData(
|
||||
height: lerpDouble(a?.height, b?.height, t),
|
||||
padding: EdgeInsets.lerp(a?.padding, b?.padding, t),
|
||||
verticalOffset: lerpDouble(a?.verticalOffset, b?.verticalOffset, t),
|
||||
preferBelow: t < 0.5 ? a.preferBelow: b.preferBelow,
|
||||
excludeFromSemantics: t < 0.5 ? a.excludeFromSemantics : b.excludeFromSemantics,
|
||||
decoration: Decoration.lerp(a?.decoration, b?.decoration, t),
|
||||
textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashValues(
|
||||
height,
|
||||
padding,
|
||||
verticalOffset,
|
||||
preferBelow,
|
||||
excludeFromSemantics,
|
||||
decoration,
|
||||
textStyle,
|
||||
waitDuration,
|
||||
showDuration,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator==(Object other) {
|
||||
if (identical(this, other))
|
||||
return true;
|
||||
if (other.runtimeType != runtimeType)
|
||||
return false;
|
||||
final TooltipThemeData typedOther = other;
|
||||
return typedOther.height == height
|
||||
&& typedOther.padding == padding
|
||||
&& typedOther.verticalOffset == verticalOffset
|
||||
&& typedOther.preferBelow == preferBelow
|
||||
&& typedOther.excludeFromSemantics == excludeFromSemantics
|
||||
&& typedOther.decoration == decoration
|
||||
&& typedOther.textStyle == textStyle
|
||||
&& typedOther.waitDuration == waitDuration
|
||||
&& typedOther.showDuration == showDuration;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DoubleProperty('height', height, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
|
||||
properties.add(DoubleProperty('vertical offset', verticalOffset, defaultValue: null));
|
||||
properties.add(FlagProperty('position', value: preferBelow, ifTrue: 'below', ifFalse: 'above', showName: true, defaultValue: null));
|
||||
properties.add(FlagProperty('semantics', value: excludeFromSemantics, ifTrue: 'excluded', showName: true, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Decoration>('decoration', decoration, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<TextStyle>('textStyle', textStyle, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Duration>('wait duration', waitDuration, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Duration>('show duration', showDuration, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
/// An inherited widget that defines the configuration for
|
||||
/// [Tooltip]s in this widget's subtree.
|
||||
///
|
||||
/// Values specified here are used for [Tooltip] properties that are not
|
||||
/// given an explicit non-null value.
|
||||
///
|
||||
/// {@tool sample}
|
||||
///
|
||||
/// Here is an example of a tooltip theme that applies a blue foreground
|
||||
/// with non-rounded corners.
|
||||
///
|
||||
/// ```dart
|
||||
/// TooltipTheme(
|
||||
/// decoration: BoxDecoration(
|
||||
/// color: Colors.blue.withOpacity(0.9),
|
||||
/// borderRadius: BorderRadius.zero,
|
||||
/// ),
|
||||
/// child: Tooltip(
|
||||
/// message: 'Example tooltip',
|
||||
/// child: IconButton(
|
||||
/// iconSize: 36.0,
|
||||
/// icon: Icon(Icons.touch_app),
|
||||
/// onPressed: () {},
|
||||
/// ),
|
||||
/// ),
|
||||
/// ),
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
class TooltipTheme extends InheritedWidget {
|
||||
/// Creates a tooltip theme that controls the configurations for
|
||||
/// [Tooltip].
|
||||
TooltipTheme({
|
||||
Key key,
|
||||
double height,
|
||||
EdgeInsetsGeometry padding,
|
||||
double verticalOffset,
|
||||
bool preferBelow,
|
||||
bool excludeFromSemantics,
|
||||
Decoration decoration,
|
||||
TextStyle textStyle,
|
||||
Duration waitDuration,
|
||||
Duration showDuration,
|
||||
Widget child,
|
||||
}) : data = TooltipThemeData(
|
||||
height: height,
|
||||
padding: padding,
|
||||
verticalOffset: verticalOffset,
|
||||
preferBelow: preferBelow,
|
||||
excludeFromSemantics: excludeFromSemantics,
|
||||
decoration: decoration,
|
||||
textStyle: textStyle,
|
||||
waitDuration: waitDuration,
|
||||
showDuration: showDuration,
|
||||
),
|
||||
super(key: key, child: child);
|
||||
|
||||
/// The properties for descendant [Tooltip] widgets.
|
||||
final TooltipThemeData data;
|
||||
|
||||
/// Returns the [data] from the closest [TooltipTheme] ancestor. If there is
|
||||
/// no ancestor, it returns [ThemeData.tooltipTheme]. Applications can assume
|
||||
/// that the returned value will not be null.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// TooltipThemeData theme = TooltipTheme.of(context);
|
||||
/// ```
|
||||
static TooltipThemeData of(BuildContext context) {
|
||||
final TooltipTheme tooltipTheme = context.inheritFromWidgetOfExactType(TooltipTheme);
|
||||
return tooltipTheme?.data ?? Theme.of(context).tooltipTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(TooltipTheme oldWidget) => data != oldWidget.data;
|
||||
}
|
|
@ -423,6 +423,81 @@ void main() {
|
|||
expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(324.0));
|
||||
}, skip: isBrowser);
|
||||
|
||||
testWidgets('Default tooltip message textStyle - light', (WidgetTester tester) async {
|
||||
final GlobalKey key = GlobalKey();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Tooltip(
|
||||
key: key,
|
||||
message: tooltipText,
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
color: Colors.green[500],
|
||||
),
|
||||
),
|
||||
));
|
||||
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
|
||||
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||
|
||||
final TextStyle textStyle = tester.widget<Text>(find.text(tooltipText)).style;
|
||||
expect(textStyle.color, Colors.white);
|
||||
expect(textStyle.fontFamily, 'Roboto');
|
||||
expect(textStyle.decoration, TextDecoration.none);
|
||||
expect(textStyle.debugLabel, '((englishLike body1 2014).merge(blackMountainView body1)).copyWith');
|
||||
});
|
||||
|
||||
testWidgets('Default tooltip message textStyle - dark', (WidgetTester tester) async {
|
||||
final GlobalKey key = GlobalKey();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
home: Tooltip(
|
||||
key: key,
|
||||
message: tooltipText,
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
color: Colors.green[500],
|
||||
),
|
||||
),
|
||||
));
|
||||
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
|
||||
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||
|
||||
final TextStyle textStyle = tester.widget<Text>(find.text(tooltipText)).style;
|
||||
expect(textStyle.color, Colors.black);
|
||||
expect(textStyle.fontFamily, 'Roboto');
|
||||
expect(textStyle.decoration, TextDecoration.none);
|
||||
expect(textStyle.debugLabel, '((englishLike body1 2014).merge(whiteMountainView body1)).copyWith');
|
||||
});
|
||||
|
||||
testWidgets('Custom tooltip message textStyle', (WidgetTester tester) async {
|
||||
final GlobalKey key = GlobalKey();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Tooltip(
|
||||
key: key,
|
||||
textStyle: const TextStyle(
|
||||
color: Colors.orange,
|
||||
decoration: TextDecoration.underline
|
||||
),
|
||||
message: tooltipText,
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
color: Colors.green[500],
|
||||
),
|
||||
),
|
||||
));
|
||||
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
|
||||
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||
|
||||
final TextStyle textStyle = tester.widget<Text>(find.text(tooltipText)).style;
|
||||
expect(textStyle.color, Colors.orange);
|
||||
expect(textStyle.fontFamily, null);
|
||||
expect(textStyle.decoration, TextDecoration.underline);
|
||||
});
|
||||
|
||||
testWidgets('Does tooltip end up with the right default size, shape, and color', (WidgetTester tester) async {
|
||||
final GlobalKey key = GlobalKey();
|
||||
await tester.pumpWidget(
|
||||
|
@ -856,7 +931,6 @@ void main() {
|
|||
|
||||
expect(description, <String>[
|
||||
'"message"',
|
||||
'position: below',
|
||||
]);
|
||||
});
|
||||
testWidgets('Tooltip implements debugFillProperties', (WidgetTester tester) async {
|
||||
|
|
1118
packages/flutter/test/material/tooltip_theme_test.dart
Normal file
1118
packages/flutter/test/material/tooltip_theme_test.dart
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue