diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart index 6c187c27e0..73a45f6169 100644 --- a/packages/flutter/lib/src/material/button.dart +++ b/packages/flutter/lib/src/material/button.dart @@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart'; import 'colors.dart'; +import 'constants.dart'; import 'debug.dart'; import 'ink_well.dart'; import 'material.dart'; @@ -190,8 +191,9 @@ abstract class MaterialButtonState extends State { child: contents ); } else { - contents = new DefaultTextStyle( + contents = new AnimatedDefaultTextStyle( style: style, + duration: kThemeChangeDuration, child: contents ); } diff --git a/packages/flutter/lib/src/material/drawer_item.dart b/packages/flutter/lib/src/material/drawer_item.dart index f89ccee664..fce098995f 100644 --- a/packages/flutter/lib/src/material/drawer_item.dart +++ b/packages/flutter/lib/src/material/drawer_item.dart @@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart'; import 'colors.dart'; +import 'constants.dart'; import 'debug.dart'; import 'icon.dart'; import 'icons.dart'; @@ -102,8 +103,9 @@ class DrawerItem extends StatelessWidget { new Flexible( child: new Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: new DefaultTextStyle( + child: new AnimatedDefaultTextStyle( style: _getTextStyle(themeData), + duration: kThemeChangeDuration, child: child ) ) diff --git a/packages/flutter/lib/src/material/list_item.dart b/packages/flutter/lib/src/material/list_item.dart index d6a87123a7..a444050a06 100644 --- a/packages/flutter/lib/src/material/list_item.dart +++ b/packages/flutter/lib/src/material/list_item.dart @@ -4,6 +4,7 @@ import 'package:flutter/widgets.dart'; +import 'constants.dart'; import 'debug.dart'; import 'ink_well.dart'; import 'theme.dart'; @@ -166,8 +167,9 @@ class ListItem extends StatelessWidget { )); } - final Widget primaryLine = new DefaultTextStyle( + final Widget primaryLine = new AnimatedDefaultTextStyle( style: _primaryTextStyle(context), + duration: kThemeChangeDuration, child: title ?? new Container() ); Widget center = primaryLine; @@ -177,8 +179,9 @@ class ListItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ primaryLine, - new DefaultTextStyle( + new AnimatedDefaultTextStyle( style: _secondaryTextStyle(context), + duration: kThemeChangeDuration, child: subtitle ) ] diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart index 6c67f0b1e0..ebcdcfa57e 100644 --- a/packages/flutter/lib/src/material/material.dart +++ b/packages/flutter/lib/src/material/material.dart @@ -202,8 +202,9 @@ class _MaterialState extends State { Color backgroundColor = _getBackgroundColor(context); Widget contents = config.child; if (contents != null) { - contents = new DefaultTextStyle( + contents = new AnimatedDefaultTextStyle( style: config.textStyle ?? Theme.of(context).textTheme.body1, + duration: kThemeChangeDuration, child: contents ); } diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index 1c5546a828..7556fda46c 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; +import 'constants.dart'; import 'divider.dart'; import 'icon.dart'; import 'icons.dart'; @@ -90,8 +91,9 @@ class _PopupMenuItemState> extends State { if (!config.enabled) style = style.copyWith(color: theme.disabledColor); - Widget item = new DefaultTextStyle( + Widget item = new AnimatedDefaultTextStyle( style: style, + duration: kThemeChangeDuration, child: new Baseline( baseline: config.height - _kBaselineOffsetFromBottom, child: buildChild() diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 7451a77c21..830c6bfb93 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -851,9 +851,9 @@ class _TabBarState extends ScrollableState> implements TabBarSelect indicatorColor = Colors.white; } - TextStyle textStyle = themeData.primaryTextTheme.body2; - IconThemeData iconTheme = themeData.primaryIconTheme; - Color textColor = themeData.primaryTextTheme.body2.color.withAlpha(0xB2); // 70% alpha + final TextStyle textStyle = themeData.primaryTextTheme.body2; + final IconThemeData iconTheme = themeData.primaryIconTheme; + final Color textColor = themeData.primaryTextTheme.body2.color.withAlpha(0xB2); // 70% alpha List tabs = []; bool textAndIcons = false; diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart index 1e7fc665f5..05ab2c935c 100644 --- a/packages/flutter/lib/src/widgets/implicit_animations.dart +++ b/packages/flutter/lib/src/widgets/implicit_animations.dart @@ -48,6 +48,16 @@ class Matrix4Tween extends Tween { } } +/// An interpolation between two [TextStyle]s. +/// +/// This will not work well if the styles don't set the same fields. +class TextStyleTween extends Tween { + TextStyleTween({ TextStyle begin, TextStyle end }) : super(begin: begin, end: end); + + @override + TextStyle lerp(double t) => TextStyle.lerp(begin, end, t); +} + /// An abstract widget for building widgets that gradually change their /// values over a period of time. abstract class ImplicitlyAnimatedWidget extends StatefulWidget { @@ -504,3 +514,55 @@ class _AnimatedOpacityState extends AnimatedWidgetBaseState { ); } } + +/// Animated version of [DefaultTextStyle] which automatically +/// transitions the default text style (the text style to apply to +/// descendant [Text] widgets without explicit style) over a given +/// duration whenever the given style changes. +class AnimatedDefaultTextStyle extends ImplicitlyAnimatedWidget { + AnimatedDefaultTextStyle({ + Key key, + this.child, + this.style, + Curve curve: Curves.linear, + Duration duration + }) : super(key: key, curve: curve, duration: duration) { + assert(style != null); + assert(child != null); + } + + /// The widget below this widget in the tree. + final Widget child; + + /// The target text style. + /// + /// The text style must not be null. + final TextStyle style; + + @override + _AnimatedDefaultTextStyleState createState() => new _AnimatedDefaultTextStyleState(); + + @override + void debugFillDescription(List description) { + super.debugFillDescription(description); + '$style'.split('\n').forEach(description.add); + } +} + +class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState { + TextStyleTween _style; + + @override + void forEachTween(TweenVisitor visitor) { + // TODO(ianh): Use constructor tear-offs when it becomes possible + _style = visitor(_style, config.style, (dynamic value) => new TextStyleTween(begin: value)); + } + + @override + Widget build(BuildContext context) { + return new DefaultTextStyle( + style: _style.evaluate(animation), + child: config.child + ); + } +}