inherited_theme.dart 5.34 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
// 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 'framework.dart';

/// An [InheritedWidget] that defines visual properties like colors
/// and text styles, which the [child]'s subtree depends on.
///
/// The [wrap] method is used by [captureAll] to construct a widget
/// that will wrap a child in all of the inherited themes which
/// are present in a build context but are not present in the
/// context that the returned widget is eventually built in.
///
/// A widget that's shown in a different context from the one it's
/// built in, like the contents of a new route or an overlay, will
/// be able to depend on inherited widget ancestors of the context
/// it's built in.
///
/// {@tool snippet --template=freeform}
/// This example demonstrates how `InheritedTheme.captureAll()` can be used
/// to wrap the contents of a new route with the inherited themes that
/// are present when the route is built - but are not present when route
/// is actually shown.
///
/// If the same code is run without `InheritedTheme.captureAll(), the
/// new route's Text widget will inherit the "something must be wrong"
/// fallback text style, rather than the default text style defined in MyApp.
///
/// ```dart imports
/// import 'package:flutter/material.dart';
/// ```
///
/// ```dart main
/// void main() {
///   runApp(MyApp());
/// }
/// ```
///
/// ```dart
/// class MyAppBody extends StatelessWidget {
///   @override
///   Widget build(BuildContext context) {
///     return GestureDetector(
///       onTap: () {
///         Navigator.of(context).push(
///           MaterialPageRoute(
///             builder: (BuildContext _) {
///               // InheritedTheme.captureAll() saves references to themes that
///               // are found above the context provided to this widget's build
///               // method, notably the DefaultTextStyle defined in MyApp. The
///               // context passed to the MaterialPageRoute's builder is not used,
///               // because its ancestors are above MyApp's home.
///               return InheritedTheme.captureAll(context, Container(
///                 alignment: Alignment.center,
///                 color: Theme.of(context).colorScheme.surface,
///                 child: Text('Hello World'),
///               ));
///             },
///           ),
///         );
///       },
///       child: Center(child: Text('Tap Here')),
///     );
///   }
/// }
///
/// class MyApp extends StatelessWidget {
///   @override
///   Widget build(BuildContext context) {
///     return MaterialApp(
///       home: Scaffold(
///         // Override the DefaultTextStyle defined by the Scaffold.
///         // Descendant widgets will inherit this big blue text style.
///         body: DefaultTextStyle(
///           style: TextStyle(fontSize: 48, color: Colors.blue),
///           child: MyAppBody(),
///         ),
///       ),
///     );
///   }
/// }
/// ```
/// {@end-tool}
abstract class InheritedTheme extends InheritedWidget {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.

  const InheritedTheme({
    Key key,
    @required Widget child,
  }) : super(key: key, child: child);

  /// Return a copy of this inherited theme with the specified [child].
  ///
  /// If the identical inherited theme is already visible from [context] then
  /// just return the [child].
  ///
  /// This implementation for [TooltipTheme] is typical:
  /// ```dart
  /// Widget wrap(BuildContext context, Widget child) {
104
  ///   final TooltipTheme ancestorTheme = context.findAncestorWidgetOfExactType<TooltipTheme>());
105 106 107 108 109 110 111 112 113 114 115 116
  ///   return identical(this, ancestorTheme) ? child : TooltipTheme(data: data, child: child);
  /// }
  /// ```
  Widget wrap(BuildContext context, Widget child);

  /// Returns a widget that will [wrap] child in all of the inherited themes
  /// which are visible from [context].
  static Widget captureAll(BuildContext context, Widget child) {
    assert(child != null);
    assert(context != null);

    final List<InheritedTheme> themes = <InheritedTheme>[];
117
    final Set<Type> themeTypes = <Type>{};
118 119
    context.visitAncestorElements((Element ancestor) {
      if (ancestor is InheritedElement && ancestor.widget is InheritedTheme) {
120
        final InheritedTheme theme = ancestor.widget as InheritedTheme;
121 122 123 124 125 126 127 128
        final Type themeType = theme.runtimeType;
        // Only remember the first theme of any type. This assumes
        // that inherited themes completely shadow ancestors of the
        // the same type.
        if (!themeTypes.contains(themeType)) {
          themeTypes.add(themeType);
          themes.add(theme);
        }
129 130 131 132 133 134 135 136 137 138 139 140
      }
      return true;
    });

    return _CaptureAll(themes: themes, child: child);
  }
}

class _CaptureAll extends StatelessWidget {
  const _CaptureAll({
    Key key,
    @required this.themes,
141
    @required this.child,
142 143 144 145 146 147 148 149
  }) : assert(themes != null), assert(child != null), super(key: key);

  final List<InheritedTheme> themes;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    Widget wrappedChild = child;
150
    for (final InheritedTheme theme in themes)
151 152 153 154
      wrappedChild = theme.wrap(context, wrappedChild);
    return wrappedChild;
  }
}