mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
This attempts to apply some of what we learnt from UX studies, namely that people wonder how to add multiple children to widget that take one child.
257 lines
8.9 KiB
Dart
257 lines
8.9 KiB
Dart
// Copyright 2015 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
|
|
import 'material_localizations.dart';
|
|
import 'theme_data.dart';
|
|
import 'typography.dart';
|
|
|
|
export 'theme_data.dart' show Brightness, ThemeData;
|
|
|
|
/// The duration over which theme changes animate by default.
|
|
const Duration kThemeAnimationDuration = const Duration(milliseconds: 200);
|
|
|
|
/// Applies a theme to descendant widgets.
|
|
///
|
|
/// A theme describes the colors and typographic choices of an application.
|
|
///
|
|
/// Descendant widgets obtain the current theme's [ThemeData] object using
|
|
/// [Theme.of]. When a widget uses [Theme.of], it is automatically rebuilt if
|
|
/// the theme later changes, so that the changes can be applied.
|
|
///
|
|
/// The [Theme] widget implies an [IconTheme] widget, set to the value of the
|
|
/// [ThemeData.iconTheme] of the [data] for the [Theme].
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [ThemeData], which describes the actual configuration of a theme.
|
|
/// * [AnimatedTheme], which animates the [ThemeData] when it changes rather
|
|
/// than changing the theme all at once.
|
|
/// * [MaterialApp], which includes an [AnimatedTheme] widget configured via
|
|
/// the [MaterialApp.theme] argument.
|
|
class Theme extends StatelessWidget {
|
|
/// Applies the given theme [data] to [child].
|
|
///
|
|
/// The [data] and [child] arguments must not be null.
|
|
const Theme({
|
|
Key key,
|
|
@required this.data,
|
|
this.isMaterialAppTheme: false,
|
|
@required this.child,
|
|
}) : assert(child != null),
|
|
assert(data != null),
|
|
super(key: key);
|
|
|
|
/// Specifies the color and typography values for descendant widgets.
|
|
final ThemeData data;
|
|
|
|
/// True if this theme was installed by the [MaterialApp].
|
|
///
|
|
/// When an app uses the [Navigator] to push a route, the route's widgets
|
|
/// will only inherit from the app's theme, even though the widget that
|
|
/// triggered the push may inherit from a theme that "shadows" the app's
|
|
/// theme because it's deeper in the widget tree. Apps can find the shadowing
|
|
/// theme with `Theme.of(context, shadowThemeOnly: true)` and pass it along
|
|
/// to the class that creates a route's widgets. Material widgets that push
|
|
/// routes, like [PopupMenuButton] and [DropdownButton], do this.
|
|
final bool isMaterialAppTheme;
|
|
|
|
/// The widget below this widget in the tree.
|
|
///
|
|
/// {@macro flutter.widgets.child}
|
|
final Widget child;
|
|
|
|
static final ThemeData _kFallbackTheme = new ThemeData.fallback();
|
|
|
|
/// The data from the closest [Theme] instance that encloses the given
|
|
/// context.
|
|
///
|
|
/// If the given context is enclosed in a [Localizations] widget providing
|
|
/// [MaterialLocalizations], the returned data is localized according to the
|
|
/// nearest available [MaterialLocalizations].
|
|
///
|
|
/// Defaults to [new ThemeData.fallback] if there is no [Theme] in the given
|
|
/// build context.
|
|
///
|
|
/// If [shadowThemeOnly] is true and the closest [Theme] ancestor was
|
|
/// installed by the [MaterialApp] — in other words if the closest [Theme]
|
|
/// ancestor does not shadow the application's theme — then this returns null.
|
|
/// This argument should be used in situations where its useful to wrap a
|
|
/// route's widgets with a [Theme], but only when the application's overall
|
|
/// theme is being shadowed by a [Theme] widget that is deeper in the tree.
|
|
/// See [isMaterialAppTheme].
|
|
///
|
|
/// Typical usage is as follows:
|
|
///
|
|
/// ```dart
|
|
/// @override
|
|
/// Widget build(BuildContext context) {
|
|
/// return new Text(
|
|
/// 'Example',
|
|
/// style: Theme.of(context).textTheme.title,
|
|
/// );
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// When the [Theme] is actually created in the same `build` function
|
|
/// (possibly indirectly, e.g. as part of a [MaterialApp]), the `context`
|
|
/// argument to the `build` function can't be used to find the [Theme] (since
|
|
/// it's "above" the widget being returned). In such cases, the following
|
|
/// technique with a [Builder] can be used to provide a new scope with a
|
|
/// [BuildContext] that is "under" the [Theme]:
|
|
///
|
|
/// ```dart
|
|
/// @override
|
|
/// Widget build(BuildContext context) {
|
|
/// return new MaterialApp(
|
|
/// theme: new ThemeData.light(),
|
|
/// body: new Builder(
|
|
/// // Create an inner BuildContext so that we can refer to
|
|
/// // the Theme with Theme.of().
|
|
/// builder: (BuildContext context) {
|
|
/// return new Center(
|
|
/// child: new Text(
|
|
/// 'Example',
|
|
/// style: Theme.of(context).textTheme.title,
|
|
/// ),
|
|
/// );
|
|
/// },
|
|
/// ),
|
|
/// );
|
|
/// }
|
|
/// ```
|
|
static ThemeData of(BuildContext context, { bool shadowThemeOnly: false }) {
|
|
final _InheritedTheme inheritedTheme =
|
|
context.inheritFromWidgetOfExactType(_InheritedTheme);
|
|
if (shadowThemeOnly) {
|
|
if (inheritedTheme == null || inheritedTheme.theme.isMaterialAppTheme)
|
|
return null;
|
|
return inheritedTheme.theme.data;
|
|
}
|
|
|
|
final ThemeData colorTheme = (inheritedTheme != null) ? inheritedTheme.theme.data : _kFallbackTheme;
|
|
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
|
final TextTheme geometryTheme = localizations?.localTextGeometry ?? MaterialTextGeometry.englishLike;
|
|
return ThemeData.localize(colorTheme, geometryTheme);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return new _InheritedTheme(
|
|
theme: this,
|
|
child: new IconTheme(
|
|
data: data.iconTheme,
|
|
child: child,
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void debugFillProperties(DiagnosticPropertiesBuilder description) {
|
|
super.debugFillProperties(description);
|
|
description.add(new DiagnosticsProperty<ThemeData>('data', data, showName: false));
|
|
}
|
|
}
|
|
|
|
class _InheritedTheme extends InheritedWidget {
|
|
const _InheritedTheme({
|
|
Key key,
|
|
@required this.theme,
|
|
@required Widget child
|
|
}) : assert(theme != null),
|
|
super(key: key, child: child);
|
|
|
|
final Theme theme;
|
|
|
|
@override
|
|
bool updateShouldNotify(_InheritedTheme old) => theme.data != old.theme.data;
|
|
}
|
|
|
|
/// An interpolation between two [ThemeData]s.
|
|
///
|
|
/// This class specializes the interpolation of [Tween<ThemeData>] to call the
|
|
/// [ThemeData.lerp] method.
|
|
///
|
|
/// See [Tween] for a discussion on how to use interpolation objects.
|
|
class ThemeDataTween extends Tween<ThemeData> {
|
|
/// Creates a [ThemeData] tween.
|
|
///
|
|
/// The [begin] and [end] properties must be non-null before the tween is
|
|
/// first used, but the arguments can be null if the values are going to be
|
|
/// filled in later.
|
|
ThemeDataTween({ ThemeData begin, ThemeData end }) : super(begin: begin, end: end);
|
|
|
|
@override
|
|
ThemeData lerp(double t) => ThemeData.lerp(begin, end, t);
|
|
}
|
|
|
|
/// Animated version of [Theme] which automatically transitions the colors,
|
|
/// etc, over a given duration whenever the given theme changes.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Theme], which [AnimatedTheme] uses to actually apply the interpolated
|
|
/// theme.
|
|
/// * [ThemeData], which describes the actual configuration of a theme.
|
|
/// * [MaterialApp], which includes an [AnimatedTheme] widget configured via
|
|
/// the [MaterialApp.theme] argument.
|
|
class AnimatedTheme extends ImplicitlyAnimatedWidget {
|
|
/// Creates an animated theme.
|
|
///
|
|
/// By default, the theme transition uses a linear curve. The [data] and
|
|
/// [child] arguments must not be null.
|
|
const AnimatedTheme({
|
|
Key key,
|
|
@required this.data,
|
|
this.isMaterialAppTheme: false,
|
|
Curve curve: Curves.linear,
|
|
Duration duration: kThemeAnimationDuration,
|
|
@required this.child,
|
|
}) : assert(child != null),
|
|
assert(data != null),
|
|
super(key: key, curve: curve, duration: duration);
|
|
|
|
/// Specifies the color and typography values for descendant widgets.
|
|
final ThemeData data;
|
|
|
|
/// True if this theme was created by the [MaterialApp]. See [Theme.isMaterialAppTheme].
|
|
final bool isMaterialAppTheme;
|
|
|
|
/// The widget below this widget in the tree.
|
|
///
|
|
/// {@macro flutter.widgets.child}
|
|
final Widget child;
|
|
|
|
@override
|
|
_AnimatedThemeState createState() => new _AnimatedThemeState();
|
|
}
|
|
|
|
class _AnimatedThemeState extends AnimatedWidgetBaseState<AnimatedTheme> {
|
|
ThemeDataTween _data;
|
|
|
|
@override
|
|
void forEachTween(TweenVisitor<dynamic> visitor) {
|
|
// TODO(ianh): Use constructor tear-offs when it becomes possible
|
|
_data = visitor(_data, widget.data, (dynamic value) => new ThemeDataTween(begin: value));
|
|
assert(_data != null);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return new Theme(
|
|
isMaterialAppTheme: widget.isMaterialAppTheme,
|
|
child: widget.child,
|
|
data: _data.evaluate(animation)
|
|
);
|
|
}
|
|
|
|
@override
|
|
void debugFillProperties(DiagnosticPropertiesBuilder description) {
|
|
super.debugFillProperties(description);
|
|
description.add(new DiagnosticsProperty<ThemeDataTween>('data', _data, showName: false, defaultValue: null));
|
|
}
|
|
}
|