在Flutter中,每一個Widget都是惟一標記的。這個惟一標識是有框架在編譯/渲染期間定義的。
Widget的惟一標識與其可選參數Key一致。若是不傳Flutter會爲你生成一個。
在某些狀況下,你可能須要強制指定它的key,這樣你就能根據key來訪問一個widget。
爲了實現這樣一個需求,你可使用下面輔助工具中的一個:GlobalKey,LocalKey,UniqueKey或者ObjectKey。
其中GlobalKey能夠保證在整個應用程序中惟一。git
整個應用程序惟一的key。github
Global keys能夠惟一標識elements。Global keys提供了訪問與elements關聯的其餘對象的能力,好比 StatefulWidget
s的BuildContext
, State
的BuildContext
。canvas
擁有global keys的Widgets當他們從樹的一個位置挪到另外一個位置,能夠爲子樹重定Widget。爲了可以重定父級,一個widget必須在同一個tree中,同一個動畫幀中,離開原來原位置,併到達新的位置。緩存
Global keys是至關昂貴的。若是你不須要上面列舉的功能,能夠考慮 Key
,ValueKey
,ObjectKey
或者UniqueKey
來代替。bash
你不能在同一個樹下包含兩個擁有相同global key的widget。若是嘗試這麼作將會促發運行時斷言。框架
GlobalKey
的定義以下:less
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key
複製代碼
T必需要繼承自State,能夠說這個GlobalKey專門用於組件了.ide
static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};
複製代碼
GlobalKey裏含有一個Map,key和value分別爲自身和Element。
那何時會用到這個Map尼?
跟蹤代碼很快就找到Element類的mount方法:函數
void mount(Element parent, dynamic newSlot) {
...
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._register(this);
}
...
}
複製代碼
可見GlobalKey會在組件Mount階段把自身放到一個Map裏面緩存起來。
緩存又有何做用尼?
答案依然是爲了性能。
思考一個場景,A頁面是一個商品列表有許多商品圖片(大概就單列這樣),B頁面是一個商品詳情頁(有商品大圖),當用戶在A頁面點擊一個其中詳情,可能會出現一個過渡動畫,A頁面的商品圖片慢慢放大而後下面的介紹文字也會跟着出現,而後就這樣平滑的過渡到B頁面。
此時A頁面和B頁面都其實共用了一個商品圖片的組件,B頁面不必重複建立這個組件能夠直接把A頁面的組件「借」過來。工具
總之框架要求同一個父節點下子節點的Key都是惟一的就能夠了,GlobalKey能夠保證全局是惟一的,因此GlobalKey的組件可以依附在不一樣的節點上。
而從GlobalKey對象上,你能夠獲得幾個有用的屬性currentElement,currentWidget,currentState。
接下來看一下Widget
自己的key
定義.
用來控制在樹中,一個widget如何替換另外一個widget。
若是兩個widgets的runtimeType
和key
屬性x相等(operator==
),那麼新的widget替換就得widget經過更新底層的element(調用新的widget的Element.update
)。 除此以外,舊的element將會從tree中移除,新的widget將會轉化成element,並被插入到tree中。
另外,使用GlobalKey
做爲widget的key
將容許element在tree中移動(經過變化parent),並不會丟失state.當一個新的widget被發現(它的key和type跟上一個在同一個位置的widget都不一樣), 可是有一個擁有相同global key的widget在上一幀的tree的其餘地方,那麼這個widget的element將被移動到新的位置。
一般來講,一個widget若是是另一個widget的惟一child,此時沒必要擁有一個明確的key.
這裏屢次提到element,那麼element究竟是什麼?
提到Element
,須要再提一下Widget
的官方定義。
/// Describes the configuration for an [Element].
///
/// Widgets are the central class hierarchy in the Flutter framework. A widget
/// is an immutable description of part of a user interface. Widgets can be
/// inflated into elements, which manage the underlying render tree.
複製代碼
能夠看到,Widget
的實際工做也就是描述如何建立 Element
,Widget
是一個不可變對象,它能夠被複用, 請注意,這裏的複用不是指在兩次渲染的時候將對象從舊樹中拿過來放到新樹,而是在同一個 Widget
Tree 中,某個子 Widget
能夠出現屢次,由於它只是一個 description。
Widget
只是 Element
的一個配置描述 ,告訴 Element
這個實例如何去渲染。
/// A given widget can be included in the tree zero or more times. In particular
/// a given widget can be placed in the tree multiple times. Each time a widget
/// is placed in the tree, it is inflated into an [Element], which means a
/// widget that is incorporated into the tree multiple times will be inflated
/// multiple times.
複製代碼
從上面這段註釋能夠看出,Widget 和 Element 之間是一對多的關係。實際上渲染樹是由 Element 實例的節點構成的樹,而做爲配置文件的 Widget 可能被複用到樹的多個部分,對應產生多個 Element 對象。
這也就是你每次均可以在 build() 函數中新建 widget 的緣由。構建 widget 的過程並不耗費資源,由於 Wiget 只是用來保存屬性的容器。
若是widget只是提供給用戶的包裝殼,那麼實際進行渲染的是什麼呢?
答案是RenderingObject
。有一個很好的源碼案例可參見Opacity
的源碼,地址以下.
Opacity源碼
選用這個例子的緣由是 普通的Stateless / StatefulWidget 只是將其餘 Widget 組裝起來,而 Opacity 會真正地影響 Widget 的繪製。
最終會跟到RenderOpacity
中,會看到下面的方法:
@override
void paint(PaintingContext context, Offset offset) {
context.pushOpacity(offset, _alpha, super.paint);
}
複製代碼
完整代碼
PaintingContext
就是進行繪製操做的畫布,這裏經過在 canvas
上調用名爲pushOpacity
的方法來實現不透明度的控制。
總結一下,Widget
只是一個配置,RenderObject
負責管理佈局、繪製等操做。而鏈接Widget
和RenderObject
正是Element
。
而在 Element
的源碼中,則能夠獲取到RenderObject
:
/// The render object at (or below) this location in the tree.
///
/// If this object is a [RenderObjectElement], the render object is the one at
/// this location in the tree. Otherwise, this getter will walk down the tree
/// until it finds a [RenderObjectElement].
RenderObject get renderObject {
RenderObject result;
void visit(Element element) {
assert(result == null); // this verifies that there's only one child if (element is RenderObjectElement) result = element.renderObject; else element.visitChildren(visit); } visit(this); return result; } 複製代碼
能夠大體總結出三者的關係是:配置文件 Widget 生成了 Element,然後建立 RenderObject 關聯到 Element 的內部 renderObject 對象上,最後Flutter 經過 RenderObject 數據來佈局和繪製。
參考資料以下,感謝:
www.stephenw.cc/2018/05/28/…
juejin.im/post/5b4c60…
若是你以爲這篇文章對你有益,還請幫忙轉發和點贊,萬分感謝。