flutter 创建底部弹窗
本文介绍了如何使用Flutter创建一个底部弹窗,并解释了Builder组件的作用以及BuildContext的重要性和使用方法。
import "package:flutter/material.dart"; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: const Text('Creating a Modal Bottom Sheet'), backgroundColor: Colors.greenAccent, ), body: Builder( builder: (context) { return Center( child: ElevatedButton( child: const Text('Show Modal Bottom Sheet'), onPressed: () { showModalBottomSheet( context: context, builder: (context) { return Wrap( children: const [ ListTile( leading: Icon(Icons.share), title: Text('Share'), ), ListTile( leading: Icon(Icons.copy), title: Text('Copy Link'), ), ListTile( leading: Icon(Icons.edit), title: Text('Edit'), ), ], ); }, ); }, ), ); }, ), ), ); } }
一些被遗弃的button组件和替代品
Old Widget | Old Theme | New Widget | New Theme |
---|---|---|---|
FlatButton |
ButtonTheme |
TextButton |
TextButtonTheme |
RaisedButton |
ButtonTheme |
ElevatedButton |
ElevatedButtonTheme |
OutlineButton |
ButtonTheme |
OutlinedButton |
OutlinedButtonTheme |
为什么要使用Builder?
Builder 源码
typedef WidgetBuilder = Widget Function(BuildContext context); class Builder extends StatelessWidget { const Builder({ Key key, @required this.builder, }) : assert(builder != null), super(key: key); final WidgetBuilder builder; @override Widget build(BuildContext context) => builder(context); }
build做了一件很简单的事 : 回调一个外部传入builder构建方法,传入context
为什么要传入Context?
MyApp的传入 context 内容如下
context = {StatefulElement} MyApp(dirty, state: _MyAppState#a2d65)
_parent = {RenderObjectToWidgetElement} [root](renderObject: RenderView#e2db8 NEEDS-LAYOUT NEEDS-PAINT)
_debugReassembleConfig = null
_notificationTree = null
_slot = {Object}
_depth = 2
_widget = {MyApp} MyApp
_owner = {BuildOwner}
_lifecycleState = {_ElementLifecycle} _ElementLifecycle.active
_debugForgottenChildrenWithGlobalKey = {_HashSet} {}
_inheritedWidgets = null
_dependencies = null
_hadUnsatisfiedDependencies = false
_dirty = true
_inDirtyList = false
_debugBuiltOnce = false
_debugAllowIgnoredCallsToMarkNeedsBuild = true
_child = null
_debugDoingBuild = true
_state = {_MyAppState} _MyAppState#a2d65
_didChangeDependencies = false
结构说明如下:
-
Context 的类型为 Element
-
_depth 引用深度为2
-
_child 为 null
-
_parent 当前节点之上只有一个 root 节点
查看BuildContext的源码
abstract class BuildContext {
/// The current configuration of the [Element] that is this [BuildContext].
Widget get widget;
/// The [BuildOwner] for this context. The [BuildOwner] is in charge of
/// managing the rendering pipeline for this context.
BuildOwner? get owner;
/// Whether the [widget] is currently updating the widget or render tree.
///
/// For [StatefulWidget]s and [StatelessWidget]s this flag is true while
/// their respective build methods are executing.
/// [RenderObjectWidget]s set this to true while creating or configuring their
/// associated [RenderObject]s.
/// Other [Widget] types may set this to true for conceptually similar phases
/// of their lifecycle.
///
/// When this is true, it is safe for [widget] to establish a dependency to an
/// [InheritedWidget] by calling [dependOnInheritedElement] or
/// [dependOnInheritedWidgetOfExactType].
///
/// Accessing this flag in release mode is not valid.
bool get debugDoingBuild;
/// The current [RenderObject] for the widget. If the widget is a
/// [RenderObjectWidget], this is the render object that the widget created
/// for itself. Otherwise, it is the render object of the first descendant
/// [RenderObjectWidget].
///
/// This method will only return a valid result after the build phase is
/// complete. It is therefore not valid to call this from a build method.
/// It should only be called from interaction event handlers (e.g.
/// gesture callbacks) or layout or paint callbacks. It is also not valid to
/// call if [State.mounted] returns false.
///
/// If the render object is a [RenderBox], which is the common case, then the
/// size of the render object can be obtained from the [size] getter. This is
/// only valid after the layout phase, and should therefore only be examined
/// from paint callbacks or interaction event handlers (e.g. gesture
/// callbacks).
///
/// For details on the different phases of a frame, see the discussion at
/// [WidgetsBinding.drawFrame].
///
/// Calling this method is theoretically relatively expensive (O(N) in the
/// depth of the tree), but in practice is usually cheap because the tree
/// usually has many render objects and therefore the distance to the nearest
/// render object is usually short.
RenderObject? findRenderObject();
/// The size of the [RenderBox] returned by [findRenderObject].
///
/// This getter will only return a valid result after the layout phase is
/// complete. It is therefore not valid to call this from a build method.
/// It should only be called from paint callbacks or interaction event
/// handlers (e.g. gesture callbacks).
///
/// For details on the different phases of a frame, see the discussion at
/// [WidgetsBinding.drawFrame].
///
/// This getter will only return a valid result if [findRenderObject] actually
/// returns a [RenderBox]. If [findRenderObject] returns a render object that
/// is not a subtype of [RenderBox] (e.g., [RenderView]), this getter will
/// throw an exception in debug mode and will return null in release mode.
///
/// Calling this getter is theoretically relatively expensive (O(N) in the
/// depth of the tree), but in practice is usually cheap because the tree
/// usually has many render objects and therefore the distance to the nearest
/// render object is usually short.
Size? get size;
/// Registers this build context with [ancestor] such that when
/// [ancestor]'s widget changes this build context is rebuilt.
///
/// Returns `ancestor.widget`.
///
/// This method is rarely called directly. Most applications should use
/// [dependOnInheritedWidgetOfExactType], which calls this method after finding
/// the appropriate [InheritedElement] ancestor.
///
/// All of the qualifications about when [dependOnInheritedWidgetOfExactType] can
/// be called apply to this method as well.
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect });
/// Obtains the nearest widget of the given type `T`, which must be the type of a
/// concrete [InheritedWidget] subclass, and registers this build context with
/// that widget such that when that widget changes (or a new widget of that
/// type is introduced, or the widget goes away), this build context is
/// rebuilt so that it can obtain new values from that widget.
///
/// This is typically called implicitly from `of()` static methods, e.g.
/// [Theme.of].
///
/// This method should not be called from widget constructors or from
/// [State.initState] methods, because those methods would not get called
/// again if the inherited value were to change. To ensure that the widget
/// correctly updates itself when the inherited value changes, only call this
/// (directly or indirectly) from build methods, layout and paint callbacks, or
/// from [State.didChangeDependencies].
///
/// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that
/// method, save a reference to the ancestor in [State.didChangeDependencies].
/// It is safe to use this method from [State.deactivate], which is called
/// whenever the widget is removed from the tree.
///
/// It is also possible to call this method from interaction event handlers
/// (e.g. gesture callbacks) or timers, to obtain a value once, if that value
/// is not going to be cached and reused later.
///
/// Calling this method is O(1) with a small constant factor, but will lead to
/// the widget being rebuilt more often.
///
/// Once a widget registers a dependency on a particular type by calling this
/// method, it will be rebuilt, and [State.didChangeDependencies] will be
/// called, whenever changes occur relating to that widget until the next time
/// the widget or one of its ancestors is moved (for example, because an
/// ancestor is added or removed).
///
/// The [aspect] parameter is only used when `T` is an
/// [InheritedWidget] subclasses that supports partial updates, like
/// [InheritedModel]. It specifies what "aspect" of the inherited
/// widget this context depends on.
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
/// Obtains the element corresponding to the nearest widget of the given type `T`,
/// which must be the type of a concrete [InheritedWidget] subclass.
///
/// Returns null if no such element is found.
///
/// Calling this method is O(1) with a small constant factor.
///
/// This method does not establish a relationship with the target in the way
/// that [dependOnInheritedWidgetOfExactType] does.
///
/// This method should not be called from [State.dispose] because the element
/// tree is no longer stable at that time. To refer to an ancestor from that
/// method, save a reference to the ancestor by calling
/// [dependOnInheritedWidgetOfExactType] in [State.didChangeDependencies]. It is
/// safe to use this method from [State.deactivate], which is called whenever
/// the widget is removed from the tree.
InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>();
/// Returns the nearest ancestor widget of the given type `T`, which must be the
/// type of a concrete [Widget] subclass.
///
/// In general, [dependOnInheritedWidgetOfExactType] is more useful, since
/// inherited widgets will trigger consumers to rebuild when they change. This
/// method is appropriate when used in interaction event handlers (e.g.
/// gesture callbacks) or for performing one-off tasks such as asserting that
/// you have or don't have a widget of a specific type as an ancestor. The
/// return value of a Widget's build method should not depend on the value
/// returned by this method, because the build context will not rebuild if the
/// return value of this method changes. This could lead to a situation where
/// data used in the build method changes, but the widget is not rebuilt.
///
/// Calling this method is relatively expensive (O(N) in the depth of the
/// tree). Only call this method if the distance from this widget to the
/// desired ancestor is known to be small and bounded.
///
/// This method should not be called from [State.deactivate] or [State.dispose]
/// because the widget tree is no longer stable at that time. To refer to
/// an ancestor from one of those methods, save a reference to the ancestor
/// by calling [findAncestorWidgetOfExactType] in [State.didChangeDependencies].
///
/// Returns null if a widget of the requested type does not appear in the
/// ancestors of this context.
T? findAncestorWidgetOfExactType<T extends Widget>();
/// Returns the [State] object of the nearest ancestor [StatefulWidget] widget
/// that is an instance of the given type `T`.
///
/// This should not be used from build methods, because the build context will
/// not be rebuilt if the value that would be returned by this method changes.
/// In general, [dependOnInheritedWidgetOfExactType] is more appropriate for such
/// cases. This method is useful for changing the state of an ancestor widget in
/// a one-off manner, for example, to cause an ancestor scrolling list to
/// scroll this build context's widget into view, or to move the focus in
/// response to user interaction.
///
/// In general, though, consider using a callback that triggers a stateful
/// change in the ancestor rather than using the imperative style implied by
/// this method. This will usually lead to more maintainable and reusable code
/// since it decouples widgets from each other.
///
/// Calling this method is relatively expensive (O(N) in the depth of the
/// tree). Only call this method if the distance from this widget to the
/// desired ancestor is known to be small and bounded.
///
/// This method should not be called from [State.deactivate] or [State.dispose]
/// because the widget tree is no longer stable at that time. To refer to
/// an ancestor from one of those methods, save a reference to the ancestor
/// by calling [findAncestorStateOfType] in [State.didChangeDependencies].
///
/// {@tool snippet}
///
/// ```dart
/// ScrollableState? scrollable = context.findAncestorStateOfType<ScrollableState>();
/// ```
/// {@end-tool}
T? findAncestorStateOfType<T extends State>();
/// Returns the [State] object of the furthest ancestor [StatefulWidget] widget
/// that is an instance of the given type `T`.
///
/// Functions the same way as [findAncestorStateOfType] but keeps visiting subsequent
/// ancestors until there are none of the type instance of `T` remaining.
/// Then returns the last one found.
///
/// This operation is O(N) as well though N is the entire widget tree rather than
/// a subtree.
T? findRootAncestorStateOfType<T extends State>();
/// Returns the [RenderObject] object of the nearest ancestor [RenderObjectWidget] widget
/// that is an instance of the given type `T`.
///
/// This should not be used from build methods, because the build context will
/// not be rebuilt if the value that would be returned by this method changes.
/// In general, [dependOnInheritedWidgetOfExactType] is more appropriate for such
/// cases. This method is useful only in esoteric cases where a widget needs
/// to cause an ancestor to change its layout or paint behavior. For example,
/// it is used by [Material] so that [InkWell] widgets can trigger the ink
/// splash on the [Material]'s actual render object.
///
/// Calling this method is relatively expensive (O(N) in the depth of the
/// tree). Only call this method if the distance from this widget to the
/// desired ancestor is known to be small and bounded.
///
/// This method should not be called from [State.deactivate] or [State.dispose]
/// because the widget tree is no longer stable at that time. To refer to
/// an ancestor from one of those methods, save a reference to the ancestor
/// by calling [findAncestorRenderObjectOfType] in [State.didChangeDependencies].
T? findAncestorRenderObjectOfType<T extends RenderObject>();
/// Walks the ancestor chain, starting with the parent of this build context's
/// widget, invoking the argument for each ancestor. The callback is given a
/// reference to the ancestor widget's corresponding [Element] object. The
/// walk stops when it reaches the root widget or when the callback returns
/// false. The callback must not return null.
///
/// This is useful for inspecting the widget tree.
///
/// Calling this method is relatively expensive (O(N) in the depth of the tree).
///
/// This method should not be called from [State.deactivate] or [State.dispose]
/// because the element tree is no longer stable at that time. To refer to
/// an ancestor from one of those methods, save a reference to the ancestor
/// by calling [visitAncestorElements] in [State.didChangeDependencies].
void visitAncestorElements(bool Function(Element element) visitor);
/// Walks the children of this widget.
///
/// This is useful for applying changes to children after they are built
/// without waiting for the next frame, especially if the children are known,
/// and especially if there is exactly one child (as is always the case for
/// [StatefulWidget]s or [StatelessWidget]s).
///
/// Calling this method is very cheap for build contexts that correspond to
/// [StatefulWidget]s or [StatelessWidget]s (O(1), since there's only one
/// child).
///
/// Calling this method is potentially expensive for build contexts that
/// correspond to [RenderObjectWidget]s (O(N) in the number of children).
///
/// Calling this method recursively is extremely expensive (O(N) in the number
/// of descendants), and should be avoided if possible. Generally it is
/// significantly cheaper to use an [InheritedWidget] and have the descendants
/// pull data down, than it is to use [visitChildElements] recursively to push
/// data down to them.
void visitChildElements(ElementVisitor visitor);
/// Start bubbling this notification at the given build context.
///
/// The notification will be delivered to any [NotificationListener] widgets
/// with the appropriate type parameters that are ancestors of the given
/// [BuildContext].
void dispatchNotification(Notification notification);
/// Returns a description of the [Element] associated with the current build context.
///
/// The `name` is typically something like "The element being rebuilt was".
///
/// See also:
///
/// * [Element.describeElements], which can be used to describe a list of elements.
DiagnosticsNode describeElement(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});
/// Returns a description of the [Widget] associated with the current build context.
///
/// The `name` is typically something like "The widget being rebuilt was".
DiagnosticsNode describeWidget(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty});
/// Adds a description of a specific type of widget missing from the current
/// build context's ancestry tree.
///
/// You can find an example of using this method in [debugCheckHasMaterial].
List<DiagnosticsNode> describeMissingAncestor({ required Type expectedAncestorType });
/// Adds a description of the ownership chain from a specific [Element]
/// to the error report.
///
/// The ownership chain is useful for debugging the source of an element.
DiagnosticsNode describeOwnershipChain(String name);
}
BuildContext
是一个抽象类,没有任何具体的实现 ,查看的实现会发现它有且只有一个实现类Element
BuildContext
作为一个抽象类,暴露对应接口给外部使用,具体的实现由Element
完成
No MediaQuery widget ancestor found
-
MediaQuery
是在MaterialApp
内部包含的 -
如果在
MaterialApp
并行的节点使用MediaQuery.of (context)
找不到static MediaQueryData of(BuildContext context) { assert(context != null); assert(debugCheckHasMediaQuery(context)); return context.dependOnInheritedWidgetOfExactType<MediaQuery>()!.data; }
MediaQuery.of : 它接受一个BuildContext类型的参数,并通过调用context.dependOnInheritedWidgetOfExactType方法来获得一个最近的widget的实例,其类型为MediaQuery
context.dependOnInheritedWidgetOfExactType()只会找到最近节点
查看 dependOnInheritedWidgetOfExactType 源码,如下
@override T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object? aspect}) { assert(_debugCheckStateIsActiveForAncestorLookup()); final InheritedElement? ancestor = _inheritedWidgets == null ? null : _inheritedWidgets![T]; if (ancestor != null) { return dependOnInheritedElement(ancestor, aspect: aspect) as T; } _hadUnsatisfiedDependencies = true; return null; }
再进一步到,dependOnInheritedElement 源码
@override InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) { assert(ancestor != null); _dependencies ??= HashSet<InheritedElement>(); _dependencies!.add(ancestor); ancestor.updateDependencies(this, aspect); return ancestor.widget as InheritedWidget; }
返回
ancestor.widget
,也就是_inheritedWidgets
-
每一个element mounted的时候会调用一个_updateInheritance方法
@mustCallSuper void mount(Element parent, dynamic newSlot) { //... _updateInheritance(); //... }
_updateInheritance
的实现是这样的void _updateInheritance() { assert(_active); _inheritedWidgets = _parent?._inheritedWidgets; }
_parent?._inheritedWidgets
:每一个子类中都保存一份副本,该副本始终保存了所有父节点中存在的InheritedElement。在InheritedElement中的实现是
@override void _updateInheritance() { assert(_active); final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets; if (incomingWidgets != null) _inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets); else _inheritedWidgets = HashMap<Type, InheritedElement>(); _inheritedWidgets[widget.runtimeType] = this; }
这个除了继承父节点的_inheritedWidgets外,还把自身也加进map了,到此整个链路就完成了,所以该map里面始终保存着父节点所有的InheritedElement,但是由于是map保存的,如果是父节点有两个或更多的同一类型的InheritedElement,那么Map中只会保存最近的一个,其余的会被覆盖掉,这也是为什么我们使用context.dependOnInheritedWidgetOfExactType()只会找到最近节点的原因