Commit 73c9ebab authored by Ian Hickson's avatar Ian Hickson

Reimplement the theme transition animation by actually animating the Theme.

As part of this:
 - A lot of classes got new lerp functions, including e.g. TextStyle.
 - Theme's constructor story got overhauled. You can now configure
   everything if you really want to, and we're better about defaults.
 - Material no longer automatically animates its background color.
   (It still does for its shadow.)
 - Tabs try to get the indicator color from the theme.
 - The fields in ThemeData got reordered for sanity.
 - Theme.== and Theme.hashCode got fixed.
 - Typography got a bit of a spring cleaning.

Fixes #613.
parent 098249f7
...@@ -8,6 +8,12 @@ class IconThemeData { ...@@ -8,6 +8,12 @@ class IconThemeData {
const IconThemeData({ this.color }); const IconThemeData({ this.color });
final IconThemeColor color; final IconThemeColor color;
static IconThemeData lerp(IconThemeData begin, IconThemeData end, double t) {
return new IconThemeData(
color: t < 0.5 ? begin.color : end.color
);
}
bool operator ==(dynamic other) { bool operator ==(dynamic other) {
if (other is! IconThemeData) if (other is! IconThemeData)
return false; return false;
......
...@@ -160,12 +160,18 @@ class _MaterialState extends State<Material> { ...@@ -160,12 +160,18 @@ class _MaterialState extends State<Material> {
curve: Curves.ease, curve: Curves.ease,
duration: kThemeChangeDuration, duration: kThemeChangeDuration,
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: backgroundColor,
borderRadius: kMaterialEdges[config.type], borderRadius: kMaterialEdges[config.type],
boxShadow: config.elevation == 0 ? null : elevationToShadow[config.elevation], boxShadow: config.elevation == 0 ? null : elevationToShadow[config.elevation],
shape: config.type == MaterialType.circle ? BoxShape.circle : BoxShape.rectangle shape: config.type == MaterialType.circle ? BoxShape.circle : BoxShape.rectangle
), ),
child: new Container(
decoration: new BoxDecoration(
borderRadius: kMaterialEdges[config.type],
backgroundColor: backgroundColor,
shape: config.type == MaterialType.circle ? BoxShape.circle : BoxShape.rectangle
),
child: contents child: contents
)
); );
} }
return contents; return contents;
......
...@@ -141,8 +141,9 @@ class _MaterialAppState extends State<MaterialApp> implements BindingObserver { ...@@ -141,8 +141,9 @@ class _MaterialAppState extends State<MaterialApp> implements BindingObserver {
data: new MediaQueryData(size: _size), data: new MediaQueryData(size: _size),
child: new LocaleQuery( child: new LocaleQuery(
data: _localeData, data: _localeData,
child: new Theme( child: new AnimatedTheme(
data: theme, data: theme,
duration: kThemeAnimationDuration,
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: _errorTextStyle, style: _errorTextStyle,
child: new DefaultAssetBundle( child: new DefaultAssetBundle(
......
...@@ -717,9 +717,17 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect ...@@ -717,9 +717,17 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect
ThemeData themeData = Theme.of(context); ThemeData themeData = Theme.of(context);
Color backgroundColor = Material.of(context).color; Color backgroundColor = Material.of(context).color;
Color indicatorColor = themeData.accentColor; Color indicatorColor = themeData.indicatorColor;
if (indicatorColor == backgroundColor) if (indicatorColor == backgroundColor) {
// ThemeData tries to avoid this by having indicatorColor avoid being the
// primaryColor. However, it's possible that the tab strip is on a
// Material that isn't the primaryColor. In that case, if the indicator
// color ends up clashing, then this overrides it. When that happens,
// automatic transitions of the theme will likely look ugly as the
// indicator color suddenly snaps to white at one end, but it's not clear
// how to avoid that any further.
indicatorColor = Colors.white; indicatorColor = Colors.white;
}
TextStyle textStyle = themeData.primaryTextTheme.body1; TextStyle textStyle = themeData.primaryTextTheme.body1;
IconThemeData iconTheme = themeData.primaryIconTheme; IconThemeData iconTheme = themeData.primaryIconTheme;
......
...@@ -2,12 +2,15 @@ ...@@ -2,12 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'theme_data.dart'; import 'theme_data.dart';
export 'theme_data.dart' show ThemeData, ThemeBrightness; export 'theme_data.dart' show ThemeData, ThemeBrightness;
const kThemeAnimationDuration = const Duration(milliseconds: 200);
class Theme extends InheritedWidget { class Theme extends InheritedWidget {
Theme({ Theme({
Key key, Key key,
...@@ -37,3 +40,55 @@ class Theme extends InheritedWidget { ...@@ -37,3 +40,55 @@ class Theme extends InheritedWidget {
description.add('$data'); description.add('$data');
} }
} }
/// An animated value that interpolates [BoxConstraint]s.
class AnimatedThemeDataValue extends AnimatedValue<ThemeData> {
AnimatedThemeDataValue(ThemeData begin, { ThemeData end, Curve curve, Curve reverseCurve })
: super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
ThemeData lerp(double t) => ThemeData.lerp(begin, end, t);
}
/// Animated version of [Theme] which automatically transitions the colours,
/// etc, over a given duration whenever the given theme changes.
class AnimatedTheme extends AnimatedWidgetBase {
AnimatedTheme({
Key key,
this.data,
Curve curve: Curves.linear,
Duration duration,
this.child
}) : super(key: key, curve: curve, duration: duration) {
assert(child != null);
assert(data != null);
}
final ThemeData data;
final Widget child;
_AnimatedThemeState createState() => new _AnimatedThemeState();
}
class _AnimatedThemeState extends AnimatedWidgetBaseState<AnimatedTheme> {
AnimatedThemeDataValue _data;
void forEachVariable(VariableVisitor visitor) {
// TODO(ianh): Use constructor tear-offs when it becomes possible
_data = visitor(_data, config.data, (dynamic value) => new AnimatedThemeDataValue(value));
assert(_data != null);
}
Widget build(BuildContext context) {
return new Theme(
child: config.child,
data: _data.value
);
}
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (_data != null)
description.add('$_data');
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' show Color, hashValues; import 'dart:ui' show Color, hashValues, hashList, lerpDouble;
import 'colors.dart'; import 'colors.dart';
import 'icon_theme_data.dart'; import 'icon_theme_data.dart';
...@@ -29,41 +29,109 @@ const Color _kDarkThemeSplashColor = const Color(0x40CCCCCC); ...@@ -29,41 +29,109 @@ const Color _kDarkThemeSplashColor = const Color(0x40CCCCCC);
class ThemeData { class ThemeData {
ThemeData({ ThemeData.raw({
ThemeBrightness brightness: ThemeBrightness.light, this.brightness,
Map<int, Color> primarySwatch, this.primarySwatch,
Color accentColor, this.primaryColor,
this.accentColorBrightness: ThemeBrightness.dark, this.primaryColorBrightness,
TextTheme text this.canvasColor,
}): this.brightness = brightness, this.cardColor,
this.primarySwatch = primarySwatch, this.dividerColor,
primaryColorBrightness = primarySwatch == null ? brightness : ThemeBrightness.dark, this.highlightColor,
canvasColor = brightness == ThemeBrightness.dark ? Colors.grey[850] : Colors.grey[50], this.splashColor,
cardColor = brightness == ThemeBrightness.dark ? Colors.grey[800] : Colors.white, this.unselectedColor,
dividerColor = brightness == ThemeBrightness.dark ? const Color(0x1FFFFFFF) : const Color(0x1F000000), this.disabledColor,
// Some users want the pre-multiplied color, others just want the opacity. this.accentColor,
hintColor = brightness == ThemeBrightness.dark ? const Color(0x42FFFFFF) : const Color(0x4C000000), this.accentColorBrightness,
hintOpacity = brightness == ThemeBrightness.dark ? 0.26 : 0.30, this.indicatorColor,
highlightColor = brightness == ThemeBrightness.dark ? _kDarkThemeHighlightColor : _kLightThemeHighlightColor, this.hintColor,
splashColor = brightness == ThemeBrightness.dark ? _kDarkThemeSplashColor : _kLightThemeSplashColor, this.hintOpacity,
text = brightness == ThemeBrightness.dark ? Typography.white : Typography.black { this.text,
this.primaryTextTheme,
this.primaryIconTheme
}) {
assert(brightness != null); assert(brightness != null);
// primarySwatch can be null; TODO(ianh): see https://github.com/flutter/flutter/issues/1277
if (primarySwatch == null) { assert(primaryColor != null);
if (brightness == ThemeBrightness.dark) { assert(primaryColorBrightness != null);
_primaryColor = Colors.grey[900]; assert(canvasColor != null);
} else { assert(cardColor != null);
_primaryColor = Colors.grey[100]; assert(dividerColor != null);
} assert(highlightColor != null);
} else { assert(splashColor != null);
_primaryColor = primarySwatch[500]; assert(unselectedColor != null);
assert(disabledColor != null);
assert(accentColor != null);
assert(accentColorBrightness != null);
assert(indicatorColor != null);
assert(hintColor != null);
assert(hintOpacity != null);
assert(text != null);
assert(primaryTextTheme != null);
assert(primaryIconTheme != null);
} }
if (accentColor == null) { factory ThemeData({
_accentColor = primarySwatch == null ? Colors.blue[500] : primarySwatch[500]; ThemeBrightness brightness: ThemeBrightness.light,
} else { Map<int, Color> primarySwatch,
_accentColor = accentColor; Color primaryColor,
} ThemeBrightness primaryColorBrightness,
Color canvasColor,
Color cardColor,
Color dividerColor,
Color highlightColor,
Color splashColor,
Color unselectedColor,
Color disabledColor,
Color accentColor,
ThemeBrightness accentColorBrightness: ThemeBrightness.dark,
Color indicatorColor,
Color hintColor,
double hintOpacity,
TextTheme text,
TextTheme primaryTextTheme,
IconThemeData primaryIconTheme
}) {
// brightness default is in the arguments list
bool isDark = brightness == ThemeBrightness.dark;
primaryColor ??= primarySwatch == null ? isDark ? Colors.grey[900] : Colors.grey[100] : primarySwatch[500];
primaryColorBrightness ??= primarySwatch == null ? brightness : ThemeBrightness.dark /* swatch[500] is always dark */;
canvasColor ??= isDark ? Colors.grey[850] : Colors.grey[50];
cardColor ??= isDark ? Colors.grey[800] : Colors.white;
dividerColor ??= isDark ? const Color(0x1FFFFFFF) : const Color(0x1F000000);
highlightColor ??= isDark ? _kDarkThemeHighlightColor : _kLightThemeHighlightColor;
splashColor ??= isDark ? _kDarkThemeSplashColor : _kLightThemeSplashColor;
unselectedColor ??= isDark ? Colors.white70 : Colors.black54;
disabledColor ??= isDark ? Colors.white30 : Colors.black26;
accentColor ??= primarySwatch == null ? Colors.blue[500] : primarySwatch[500];
// accentColorBrightness default is in the arguments list
indicatorColor ??= accentColor == primaryColor ? Colors.white : accentColor;
hintColor ??= isDark ? const Color(0x42FFFFFF) : const Color(0x4C000000);
hintOpacity ??= hintColor != null ? hintColor.alpha / 0xFF : isDark ? 0.26 : 0.30;
text ??= isDark ? Typography.white : Typography.black;
primaryTextTheme ??= primaryColorBrightness == ThemeBrightness.dark ? Typography.white : Typography.black;
primaryIconTheme ??= primaryColorBrightness == ThemeBrightness.dark ? const IconThemeData(color: IconThemeColor.white) : const IconThemeData(color: IconThemeColor.black);
return new ThemeData.raw(
brightness: brightness,
primarySwatch: primarySwatch,
primaryColor: primaryColor,
primaryColorBrightness: primaryColorBrightness,
canvasColor: canvasColor,
cardColor: cardColor,
dividerColor: dividerColor,
highlightColor: highlightColor,
splashColor: splashColor,
unselectedColor: unselectedColor,
disabledColor: disabledColor,
accentColor: accentColor,
accentColorBrightness: accentColorBrightness,
indicatorColor: indicatorColor,
hintColor: hintColor,
hintOpacity: hintOpacity,
text: text,
primaryTextTheme: primaryTextTheme,
primaryIconTheme: primaryIconTheme
);
} }
factory ThemeData.light() => new ThemeData(primarySwatch: Colors.blue, brightness: ThemeBrightness.light); factory ThemeData.light() => new ThemeData(primarySwatch: Colors.blue, brightness: ThemeBrightness.light);
...@@ -78,63 +146,82 @@ class ThemeData { ...@@ -78,63 +146,82 @@ class ThemeData {
/// all dark. When the ThemeBrightness is light, the canvas and card colors /// all dark. When the ThemeBrightness is light, the canvas and card colors
/// are bright, and the primary color's darkness varies as described by /// are bright, and the primary color's darkness varies as described by
/// primaryColorBrightness. The primaryColor does not contrast well with the /// primaryColorBrightness. The primaryColor does not contrast well with the
/// card and canvas colors when the brightness is dask; when the birghtness is /// card and canvas colors when the brightness is dark; when the brightness is
/// dark, use Colors.white or the accentColor for a contrasting color. /// dark, use Colors.white or the accentColor for a contrasting color.
final ThemeBrightness brightness; final ThemeBrightness brightness;
final Map<int, Color> primarySwatch; final Map<int, Color> primarySwatch;
/// The background colour for major parts of the app (toolbars, tab bars, etc)
final Color primaryColor;
/// The brightness of the primaryColor. Used to determine the colour of text and
/// icons placed on top of the primary color (e.g. toolbar text).
final ThemeBrightness primaryColorBrightness;
final Color canvasColor; final Color canvasColor;
final Color cardColor; final Color cardColor;
final Color dividerColor; final Color dividerColor;
final Color hintColor;
final Color highlightColor; final Color highlightColor;
final Color splashColor; final Color splashColor;
final Color unselectedColor;
final Color disabledColor;
/// The foreground color for widgets (knobs, text, etc)
final Color accentColor;
/// The brightness of the accentColor. Used to determine the colour of text
/// and icons placed on top of the accent color (e.g. the icons on a floating
/// action button).
final ThemeBrightness accentColorBrightness;
/// The color of the selected tab indicator in a tab strip.
final Color indicatorColor;
// Some users want the pre-multiplied color, others just want the opacity.
final Color hintColor;
final double hintOpacity; final double hintOpacity;
/// Text with a color that contrasts with the card and canvas colors. /// Text with a color that contrasts with the card and canvas colors.
final TextTheme text; final TextTheme text;
/// The background colour for major parts of the app (toolbars, tab bars, etc)
Color get primaryColor => _primaryColor;
Color _primaryColor;
/// The brightness of the primaryColor. Used to determine the colour of text and
/// icons placed on top of the primary color (e.g. toolbar text).
final ThemeBrightness primaryColorBrightness;
/// A text theme that contrasts with the primary color. /// A text theme that contrasts with the primary color.
TextTheme get primaryTextTheme { final TextTheme primaryTextTheme;
if (primaryColorBrightness == ThemeBrightness.dark)
return Typography.white;
return Typography.black;
}
IconThemeData get primaryIconTheme { final IconThemeData primaryIconTheme;
if (primaryColorBrightness == ThemeBrightness.dark)
return const IconThemeData(color: IconThemeColor.white);
return const IconThemeData(color: IconThemeColor.black);
}
Color get unselectedColor { static ThemeData lerp(ThemeData begin, ThemeData end, double t) {
if (brightness == ThemeBrightness.dark) Map<int, Color> primarySwatch;
return Colors.white70; if (begin.primarySwatch != null && end.primarySwatch != null) {
return Colors.black54; primarySwatch = <int, Color>{};
for (int index in begin.primarySwatch.keys) {
if (!end.primarySwatch.containsKey(index))
continue;
primarySwatch[index] = Color.lerp(begin.primarySwatch[index], end.primarySwatch[index], t);
} }
Color get disabledColor {
if (brightness == ThemeBrightness.dark)
return Colors.white30;
return Colors.black26;
} }
return new ThemeData.raw(
/// The foreground color for widgets (knobs, text, etc) brightness: t < 0.5 ? begin.brightness : end.brightness,
Color get accentColor => _accentColor; primarySwatch: primarySwatch,
Color _accentColor; primaryColor: Color.lerp(begin.primaryColor, end.primaryColor, t),
primaryColorBrightness: t < 0.5 ? begin.primaryColorBrightness : end.primaryColorBrightness,
/// The brightness of the accentColor. Used to determine the colour of text canvasColor: Color.lerp(begin.canvasColor, end.canvasColor, t),
/// and icons placed on top of the accent color (e.g. the icons on a floating cardColor: Color.lerp(begin.cardColor, end.cardColor, t),
/// action button). dividerColor: Color.lerp(begin.dividerColor, end.dividerColor, t),
final ThemeBrightness accentColorBrightness; highlightColor: Color.lerp(begin.highlightColor, end.highlightColor, t),
splashColor: Color.lerp(begin.splashColor, end.splashColor, t),
unselectedColor: Color.lerp(begin.unselectedColor, end.unselectedColor, t),
disabledColor: Color.lerp(begin.disabledColor, end.disabledColor, t),
accentColor: Color.lerp(begin.accentColor, end.accentColor, t),
accentColorBrightness: t < 0.5 ? begin.accentColorBrightness : end.accentColorBrightness,
indicatorColor: Color.lerp(begin.indicatorColor, end.indicatorColor, t),
hintColor: Color.lerp(begin.hintColor, end.hintColor, t),
hintOpacity: lerpDouble(begin.hintOpacity, end.hintOpacity, t),
text: TextTheme.lerp(begin.text, end.text, t),
primaryTextTheme: TextTheme.lerp(begin.primaryTextTheme, end.primaryTextTheme, t),
primaryIconTheme: IconThemeData.lerp(begin.primaryIconTheme, end.primaryIconTheme, t)
);
}
bool operator==(Object other) { bool operator==(Object other) {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType)
...@@ -142,29 +229,46 @@ class ThemeData { ...@@ -142,29 +229,46 @@ class ThemeData {
ThemeData otherData = other; ThemeData otherData = other;
return (otherData.brightness == brightness) && return (otherData.brightness == brightness) &&
(otherData.primarySwatch == primarySwatch) && (otherData.primarySwatch == primarySwatch) &&
(otherData.primaryColor == primaryColor) &&
(otherData.primaryColorBrightness == primaryColorBrightness) &&
(otherData.canvasColor == canvasColor) && (otherData.canvasColor == canvasColor) &&
(otherData.cardColor == cardColor) && (otherData.cardColor == cardColor) &&
(otherData.dividerColor == dividerColor) && (otherData.dividerColor == dividerColor) &&
(otherData.hintColor == hintColor) &&
(otherData.highlightColor == highlightColor) && (otherData.highlightColor == highlightColor) &&
(otherData.splashColor == splashColor) &&
(otherData.unselectedColor == unselectedColor) &&
(otherData.disabledColor == disabledColor) &&
(otherData.accentColor == accentColor) &&
(otherData.accentColorBrightness == accentColorBrightness) &&
(otherData.indicatorColor == indicatorColor) &&
(otherData.hintColor == hintColor) &&
(otherData.hintOpacity == hintOpacity) && (otherData.hintOpacity == hintOpacity) &&
(otherData.text == text) && (otherData.text == text) &&
(otherData.primaryColorBrightness == primaryColorBrightness) && (otherData.primaryTextTheme == primaryTextTheme) &&
(otherData.accentColorBrightness == accentColorBrightness); (otherData.primaryIconTheme == primaryIconTheme);
} }
int get hashCode { int get hashCode {
return hashValues( return hashValues(
brightness, brightness,
primarySwatch, hashList(primarySwatch.keys),
hashList(primarySwatch.values),
primaryColor,
primaryColorBrightness,
canvasColor, canvasColor,
cardColor, cardColor,
dividerColor, dividerColor,
hintColor,
highlightColor, highlightColor,
splashColor,
unselectedColor,
disabledColor,
accentColor,
accentColorBrightness,
indicatorColor,
hintColor,
hintOpacity, hintOpacity,
text, text,
primaryColorBrightness, primaryTextTheme,
accentColorBrightness primaryIconTheme
); );
} }
......
...@@ -4,8 +4,6 @@ ...@@ -4,8 +4,6 @@
// See http://www.google.com/design/spec/style/typography.html // See http://www.google.com/design/spec/style/typography.html
import 'dart:ui' show Color;
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
import 'colors.dart'; import 'colors.dart';
...@@ -13,8 +11,11 @@ import 'colors.dart'; ...@@ -13,8 +11,11 @@ import 'colors.dart';
// TODO(eseidel): Font weights are supposed to be language relative! // TODO(eseidel): Font weights are supposed to be language relative!
// TODO(jackson): Baseline should be language relative! // TODO(jackson): Baseline should be language relative!
// These values are for English-like text. // These values are for English-like text.
// TODO(ianh): There's no font-family specified here.
class TextTheme { class TextTheme {
const TextTheme._(this.display4, this.display3, this.display2, this.display1, this.headline, this.title, this.subhead, this.body2, this.body1, this.caption, this.button);
const TextTheme._black() const TextTheme._black()
: display4 = const TextStyle(inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.black54, textBaseline: TextBaseline.alphabetic), : display4 = const TextStyle(inherit: false, fontSize: 112.0, fontWeight: FontWeight.w100, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
display3 = const TextStyle(inherit: false, fontSize: 56.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic), display3 = const TextStyle(inherit: false, fontSize: 56.0, fontWeight: FontWeight.w400, color: Colors.black54, textBaseline: TextBaseline.alphabetic),
...@@ -53,26 +54,26 @@ class TextTheme { ...@@ -53,26 +54,26 @@ class TextTheme {
final TextStyle caption; final TextStyle caption;
final TextStyle button; final TextStyle button;
static TextTheme lerp(TextTheme begin, TextTheme end, double t) {
return new TextTheme._(
TextStyle.lerp(begin.display4, end.display4, t),
TextStyle.lerp(begin.display3, end.display3, t),
TextStyle.lerp(begin.display2, end.display2, t),
TextStyle.lerp(begin.display1, end.display1, t),
TextStyle.lerp(begin.headline, end.headline, t),
TextStyle.lerp(begin.title, end.title, t),
TextStyle.lerp(begin.subhead, end.subhead, t),
TextStyle.lerp(begin.body2, end.body2, t),
TextStyle.lerp(begin.body1, end.body1, t),
TextStyle.lerp(begin.caption, end.caption, t),
TextStyle.lerp(begin.button, end.button, t)
);
}
} }
class Typography { class Typography {
Typography._(); Typography._();
static const TextTheme black = const TextTheme._black(); static const TextTheme black = const TextTheme._black();
static const TextTheme white = const TextTheme._white(); static const TextTheme white = const TextTheme._white();
// TODO(abarth): Maybe this should be hard-coded in Scaffold?
static const String typeface = 'font-family: sans-serif';
// TODO(ianh): Remove this when we remove fn2, now that it's hard-coded in App.
static const TextStyle error = const TextStyle(
color: const Color(0xD0FF0000),
fontFamily: 'monospace',
fontSize: 48.0,
fontWeight: FontWeight.w900,
textAlign: TextAlign.right,
decoration: TextDecoration.underline,
decorationColor: const Color(0xFFFF00),
decorationStyle: TextDecorationStyle.double
);
} }
...@@ -127,6 +127,31 @@ class TextStyle { ...@@ -127,6 +127,31 @@ class TextStyle {
); );
} }
/// Interpolate between two text styles.
///
/// This will not work well if the styles don't set the same fields.
static TextStyle lerp(TextStyle begin, TextStyle end, double t) {
assert(begin.inherit == end.inherit);
return new TextStyle(
inherit: end.inherit,
color: Color.lerp(begin.color, end.color, t),
fontFamily: t < 0.5 ? begin.fontFamily : end.fontFamily,
fontSize: ui.lerpDouble(begin.fontSize ?? end.fontSize, end.fontSize ?? begin.fontSize, t),
// TODO(ianh): Replace next line with "fontWeight: FontWeight.lerp(begin.fontWeight, end.fontWeight, t)," once engine is revved
fontWeight: FontWeight.values[ui.lerpDouble(begin?.fontWeight.index ?? FontWeight.normal.index, end?.fontWeight.index ?? FontWeight.normal.index, t.clamp(0.0, 1.0)).round()],
fontStyle: t < 0.5 ? begin.fontStyle : end.fontStyle,
letterSpacing: ui.lerpDouble(begin.letterSpacing ?? end.letterSpacing, end.letterSpacing ?? begin.letterSpacing, t),
wordSpacing: ui.lerpDouble(begin.wordSpacing ?? end.wordSpacing, end.wordSpacing ?? begin.wordSpacing, t),
textAlign: t < 0.5 ? begin.textAlign : end.textAlign,
textBaseline: t < 0.5 ? begin.textBaseline : end.textBaseline,
height: ui.lerpDouble(begin.height ?? end.height, end.height ?? begin.height, t),
decoration: t < 0.5 ? begin.decoration : end.decoration,
decorationColor: Color.lerp(begin.decorationColor, end.decorationColor, t),
decorationStyle: t < 0.5 ? begin.decorationStyle : end.decorationStyle
);
}
ui.TextStyle get textStyle { ui.TextStyle get textStyle {
return new ui.TextStyle( return new ui.TextStyle(
color: color, color: color,
......
...@@ -317,7 +317,7 @@ class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> ...@@ -317,7 +317,7 @@ class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer>
} }
/// Animated version of [Positioned] which automatically transitions the child's /// Animated version of [Positioned] which automatically transitions the child's
/// position over a given duration whenever the given positon changes. /// position over a given duration whenever the given position changes.
/// ///
/// Only works if it's the child of a [Stack]. /// Only works if it's the child of a [Stack].
class AnimatedPositioned extends AnimatedWidgetBase { class AnimatedPositioned extends AnimatedWidgetBase {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment