| // Copyright 2014 The Flutter 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 '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 dartpad --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) { |
| /// final TooltipTheme ancestorTheme = context.findAncestorWidgetOfExactType<TooltipTheme>()); |
| /// 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>[]; |
| final Set<Type> themeTypes = <Type>{}; |
| context.visitAncestorElements((Element ancestor) { |
| if (ancestor is InheritedElement && ancestor.widget is InheritedTheme) { |
| final InheritedTheme theme = ancestor.widget as InheritedTheme; |
| 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); |
| } |
| } |
| return true; |
| }); |
| |
| return _CaptureAll(themes: themes, child: child); |
| } |
| } |
| |
| class _CaptureAll extends StatelessWidget { |
| const _CaptureAll({ |
| Key key, |
| @required this.themes, |
| @required this.child, |
| }) : assert(themes != null), assert(child != null), super(key: key); |
| |
| final List<InheritedTheme> themes; |
| final Widget child; |
| |
| @override |
| Widget build(BuildContext context) { |
| Widget wrappedChild = child; |
| for (final InheritedTheme theme in themes) |
| wrappedChild = theme.wrap(context, wrappedChild); |
| return wrappedChild; |
| } |
| } |