官方對Widget的說明:vue
本身的理解:web
官方對Element的描述:算法
官方對RenderObject的描述:數據結構
咱們這裏以Padding爲例,Padding用來設置內邊距app
Padding是一個Widget,而且繼承自SingleChildRenderObjectWidget框架
繼承關係以下:less
Padding -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget
複製代碼
咱們以前在建立Widget時,常用StatelessWidget和StatefulWidget,這種Widget只是將其餘的Widget在build方法中組裝起來,並非一個真正能夠渲染的Widget(在以前的課程中其實有提到)。dom
在Padding的類中,咱們找不到任何和渲染相關的代碼,這是由於Padding僅僅做爲一個配置信息,這個配置信息會隨着咱們設置的屬性不一樣,頻繁的銷燬和建立。編輯器
問題:頻繁的銷燬和建立會不會影響Flutter的性能呢?ide
那麼真正的渲染相關的代碼在哪裏執行呢?
咱們來看Padding裏面的代碼,有一個很是重要的方法:
@override
RenderPadding createRenderObject(BuildContext context) { return RenderPadding( padding: padding, textDirection: Directionality.of(context), ); } 複製代碼
上面的代碼建立了什麼呢?RenderPadding
RenderPadding的繼承關係是什麼呢?
RenderPadding -> RenderShiftedBox -> RenderBox -> RenderObject
複製代碼
咱們來具體查看一下RenderPadding的源代碼:
set padding(EdgeInsetsGeometry value) {
assert(value != null); assert(value.isNonNegative); if (_padding == value) return; _padding = value; _markNeedResolution(); } 複製代碼
咱們來思考一個問題:
Element何時建立?
在每一次建立Widget的時候,會建立一個對應的Element,而後將該元素插入樹中。
在SingleChildRenderObjectWidget中,咱們能夠找到以下代碼:
@override
SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this); 複製代碼
在建立完一個Element以後,Framework會調用mount方法來將Element插入到樹中具體的位置:
在調用mount方法時,會同時使用Widget來建立RenderObject,而且保持對RenderObject的引用:
@override
void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); _renderObject = widget.createRenderObject(this); assert(() { _debugUpdateRenderObjectOwner(); return true; }()); assert(_slot == newSlot); attachRenderObject(newSlot); _dirty = false; } 複製代碼
可是,若是你去看相似於Text這種組合類的Widget,它也會執行mount方法,可是mount方法中並無調用createRenderObject這樣的方法。
@override
void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); assert(_child == null); assert(_active); _firstBuild(); assert(_child != null); } void _firstBuild() { rebuild(); } 複製代碼
若是是一個StatefulWidget,則建立出來的是一個StatefulElement
咱們來看一下StatefulElement的構造器:
StatefulElement(StatefulWidget widget) : _state = widget.createState(), ....省略代碼 _state._widget = widget; 複製代碼
而調用build的時候,本質上調用的是_state中的build方法:
Widget build() => state.build(this);
複製代碼
在StatelessElement中,咱們發現是將this傳入,因此本質上BuildContext就是當前的Element
Widget build() => widget.build(this);
複製代碼
咱們來看一下繼承關係圖:
abstract class Element extends DiagnosticableTree implements BuildContext 複製代碼
在StatefulElement中,build方法也是相似,調用state的build方式時,傳入的是this
Widget build() => state.build(this);
複製代碼
Widget只是描述了配置信息:
Element是真正保存樹結構的對象:
RenderObject是真正渲染的對象:
markNeedsLayout
performLayout
markNeedsPaint
paint
等方法
在咱們建立Widget的時候,老是會看到一個key的參數,它又是作什麼的呢?
咱們一塊兒來作一個key的案例需求
home界面的基本代碼:
class _HYHomePageState extends State<HYHomePage> {
List<String> names = ["aaa", "bbb", "ccc"]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Test Key"), ), body: ListView( children: names.map((name) { return ListItemLess(name); }).toList(), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.delete), onPressed: () { setState(() { names.removeAt(0); }); } ), ); } } 複製代碼
注意:待會兒咱們會修改返回的ListItem爲ListItemLess或者ListItemFul
咱們先對ListItem使用一個StatelessWidget進行實現:
class ListItemLess extends StatelessWidget {
final String name; final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)); ListItemLess(this.name); @override Widget build(BuildContext context) { return Container( height: 60, child: Text(name), color: randomColor, ); } } 複製代碼
它的實現效果是每刪除一個,全部的顏色都會發現一次變化
咱們對ListItem使用StatefulWidget來實現
class ListItemFul extends StatefulWidget {
final String name; ListItemFul(this.name): super(); @override _ListItemFulState createState() => _ListItemFulState(); } class _ListItemFulState extends State<ListItemFul> { final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)); @override Widget build(BuildContext context) { return Container( height: 60, child: Text(widget.name), color: randomColor, ); } } 複製代碼
咱們發現一個很奇怪的現象,顏色不變化,可是數據向上移動了
咱們使用一個隨機的key
ListItemFul的修改以下:
class ListItemFul extends StatefulWidget {
final String name; ListItemFul(this.name, {Key key}): super(key: key); @override _ListItemFulState createState() => _ListItemFulState(); } 複製代碼
home界面代碼修改以下:
body: ListView(
children: names.map((name) { return ListItemFul(name, key: ValueKey(Random().nextInt(10000)),); }).toList(), ), 複製代碼
這一次咱們發現,每次刪除都會出現隨機顏色的現象:
// Widget類中的代碼
static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } 複製代碼
此次,咱們將name做爲key來看一下結果:
body: ListView(
children: names.map((name) { return ListItemFul(name, key: ValueKey(name)); }).toList(), ), 複製代碼
咱們理想中的效果:
Key自己是一個抽象,不過它也有一個工廠構造器,建立出來一個ValueKey
直接子類主要有:LocalKey和GlobalKey
LocalKey有三個子類
ValueKey:
ObjectKey:
UniqueKey
GlobalKey能夠幫助咱們訪問某個Widget的信息,包括Widget或State或Element等對象
咱們來看下面的例子:我但願能夠在HYHomePage中直接訪問HYHomeContent中的內容
class HYHomePage extends StatelessWidget {
final GlobalKey<_HYHomeContentState> homeKey = GlobalKey(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("列表測試"), ), body: HYHomeContent(key: homeKey), floatingActionButton: FloatingActionButton( child: Icon(Icons.data_usage), onPressed: () { print("${homeKey.currentState.value}"); print("${homeKey.currentState.widget.name}"); print("${homeKey.currentContext}"); }, ), ); } } class HYHomeContent extends StatefulWidget { final String name = "123"; HYHomeContent({Key key}): super(key: key); @override _HYHomeContentState createState() => _HYHomeContentState(); } class _HYHomeContentState extends State<HYHomeContent> { final String value = "abc"; @override Widget build(BuildContext context) { return Container(); } } 複製代碼
備註:全部內容首發於公衆號,以後除了Flutter也會更新其餘技術文章,TypeScript、React、Node、uniapp、mpvue、數據結構與算法等等,也會更新一些本身的學習心得等,歡迎你們關注