summary
[BuildContext] objects are actually [Element] objects. The [BuildContext] ,interface is used to discourage direct manipulation of [Element] objects.
The translation means that the [BuildContext] object is actually a [Element] object. The [BuildContext] interface is used to prevent direct manipulation of [Element] objects.
According to the official comments, we can know that BuildContext is actually an Element object, mainly to prevent developers from directly operating the Element object. Through the source code, we can also see that Element implements the abstract class BuildContext
abstract class Element extends DiagnosticableTree implements BuildContext {}
Role of BuildContext
Before An article The corresponding relationship between Element and Widget has been described in. You can take a look at those that are not very clear
Element is the instance corresponding to a specific position in the Widget tree, and the state of the Widget will be saved in element.
So what can BuildContext do? BuildContext can do almost anything that Element can do, such as:
var size = (context.findRenderObjec var size = (context.findRenderObject() as RenderBox).size; var local = (context.findRenderObject() as RenderBox).localToGlobal; var widget = context.widget;t() as RenderBox).size;
For example, the width and height, the offset from the upper left corner, and the widget corresponding to element are obtained through the context above
Because Element inherits from BuildContext, we can even refresh the state of Element directly through context, as follows:
(context as Element).markNeedsBuild();
In this way, you can refresh the current Element directly without going through SetState, but this is extremely not recommended.
In fact, in SetState, the markNeedsBuild method is finally called, as follows:
void setState(VoidCallback fn) { assert(fn != null); assert(() { if (_debugLifecycleState == _StateLifecycle.defunct) { ///...... } if (_debugLifecycleState == _StateLifecycle.created && !mounted) { ///...... } return true; }()); final dynamic result = fn() as dynamic; assert(() { if (result is Future) { ///...... ]); } return true; }()); ///Final call _element!.markNeedsBuild(); }
In the process of writing code, we will also find a problem, that is, the state to be updated does not have to be written in setState, but only on setState. This is no problem. For example, some other responsive frameworks do not have this callback, but only provide a method to notify page refresh, and the same is true for earlier flutter s. However, the disadvantages of this problem are finally found. For example, most people will add a setState after each method, resulting in excessive overhead, and they don't know whether the setState has practical significance when deleting it, which will cause some unnecessary trouble.
Therefore, Flutter adds a callback in setState. We can put the updated state directly in the callback, and those that have nothing to do with the state can be placed outside.
Some common methods
-
(context as Element).findAncestorStateOfType()
Look up along the current Element until a specific type returns its State
-
(context as Element).findRenderObject()
Gets the object rendered by Element
-
(context as Element).findAncestorRenderObjectOfType()
Traverse up to get the render object corresponding to the generic
-
(context as Element).findAncestorWidgetOfExactType()
Traverse to get the Widget corresponding to T
There are some chestnuts used in the above methods in the source code, such as:
- Scaffold.of(context).showSnackBar()
A SnackBar is displayed at the bottom of Scaffold
static ScaffoldState of(BuildContext context) { assert(context != null); final ScaffoldState? result = context.findAncestorStateOfType<ScaffoldState>(); if (result != null) return result; //...... }
Looking at the of method, you can find that the findAncestorStateOfType method is used to obtain the Scaffold state and finally implement some operations,
-
Theme.of(context).accentColor
We can obtain the following theme colors through the above method, and its internal implementation is as follows:
static ThemeData of(BuildContext context) { final _InheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>(); final MaterialLocalizations? localizations = Localizations.of<MaterialLocalizations>(context, MaterialLocalizations); final ScriptCategory category = localizations?.scriptCategory ?? ScriptCategory.englishLike; final ThemeData theme = inheritedTheme?.theme.data ?? _kFallbackTheme; return ThemeData.localize(theme, theme.typography.geometryThemeFor(category)); }
It's the same as the one above. It's also the one closest to you_ Inherited theme, I'll give it back to you at last
Chestnuts
Write a sidebar and open the sidebar by clicking the button
class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), drawer: Drawer(), floatingActionButton: FloatingActionButton(onPressed: () { Scaffold.of(context).openDrawer(); }), ); } }
Run the code and you will find the error: scaffold of() called with a context that does not contain a Scaffold.
This means that no Drawer can be found in the current context, so it cannot be opened.
Why? Because this context is at the current MyHomePage level, there is no Drawer on its upper layer, so naturally there is no way to open it. So how to solve it? As follows:
class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), drawer: Drawer(), floatingActionButton: Floating()); } } class Floating extends StatelessWidget { @override Widget build(BuildContext context) { return FloatingActionButton(onPressed: () { Scaffold.of(context).openDrawer(); }); } }
It can be solved by modifying to the above code.
The context in Floating is the level below MyHomePage, so if its superior Scaffold, it will not report an error.
However, in general, we do not need to create one more component, so we need a better solution, as follows:
class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), drawer: Drawer(), floatingActionButton: Builder( builder: (context) { return FloatingActionButton(onPressed: () { Scaffold.of(context).openDrawer(); }); }, )); } }
We can create an anonymous component through Builder.
reference
Uncle Wang is not bald at station B
If this article can help you, it's a great honor. If there are errors and questions in this article, you are welcome to put forward them!