• <ul id="cgeq2"></ul>
  • 歡迎您光臨深圳塔燈網絡科技有限公司!
    電話圖標 余先生:13699882642

    網站百科

    為您解碼網站建設的點點滴滴

    深入了解Flutter界面開發

    發表日期:2018-07 文章編輯:小燈 瀏覽次數:2602

    作者:閑魚技術-朝空

    概要

    本文不是flutter界面開發入門文章,而是一篇深入介紹Flutter framework關于視圖樹的創建與管理機制、布局、渲染的原理、以及flutter布局與渲染相關性能優化的設計思路的文章。同時介紹在使用flutter開發過程中,遇到的一些坑和相應的解決方案。

    Flutter框架簡介

    image
    1. 跨平臺應用的框架,沒有使用WebView或者系統平臺自帶的控件,使用自身的高性能渲染引擎(Skia)自繪,
    2. 界面開發語言使用dart,底層渲染引擎使用C, C++
    3. 組合大于繼承,控件本身通常由許多小型、單用途的控件組成,結合起來產生強大的效果,類的層次結構是扁平的,以最大化可能的組合數量

    Rendering Pipeline

    image

    本文主要介紹build、layout、paint的三個階段

    視圖樹

    Widget&Element&RenderObject

    image

    flutter視圖樹包含了三種樹,上圖只是介紹了三顆樹的基礎class的對應關系和功能介紹

    創建樹

    1. 創建widget樹
    2. 調用runApp(rootWidget),將rootWidget傳給rootElement,做為rootElement的子節點,生成Element樹,由Element樹生成Render樹


      image
    • Widget:存放渲染內容、視圖布局信息,widget的屬性最好都是immutable(如何更新數據呢?查看后續內容)
    • Element:存放上下文,通過Element遍歷視圖樹,Element同時持有Widget和RenderObject
    • RenderObject:根據Widget的布局屬性進行layout,paint Widget傳人的內容

    更新樹

    為什么widget都是immutable?

    flutter界面開發是一種響應式編程,主張simple is fast,flutter設計的初衷希望數據變更時發送通知到對應的可變更節點(可能是一個StatefullWidget子節點,也可以是rootWidget),由上到下重新create widget樹進行刷新,這種思路比較簡單,不用關心數據變更會影響到哪些節點。

    widget重新創建,element樹和renderObject樹是否也重新創建?

    widget只是一個配置數據結構,創建是非常輕量的,加上flutter團隊對widget的創建/銷毀做了優化,不用擔心整個widget樹重新創建所帶來的性能問題,但是renderobject就不一樣了,renderobject涉及到layout、paint等復雜操作,是一個真正渲染的view,整個view 樹重新創建開銷就比較大,所以答案是否定的。

    樹的更新規則

    1. 找到widget對應的element節點,設置element為dirty,觸發drawframe, drawframe會調用element的performRebuild()進行樹重建
    2. widget.build() == null, deactive element.child,刪除子樹,流程結束
    3. element.child.widget == NULL, mount 的新子樹,流程結束
    4. element.child.widget == widget.build() 無需重建,否則進入流程5
    5. Widget.canUpdate(element.child.widget, newWidget) == true,更新child的slot,element.child.update(newWidget)(如果child還有子節點,則遞歸上面的流程進行子樹更新),流程結束,否則轉6
    6. Widget.canUpdate(element.child.widget, newWidget) != true(widget的classtype 或者 key 不相等),deactivew element.child,mount 新子樹

    注意事項:

    1. element.child.widget == widget.build(),不會觸發子樹的update,當觸發update的時候,如果沒有生效,要注意widget是否使用舊widget,沒有new widget,導致update流程走到該widget就停止了
    2. 子樹的深度變化,會引起子樹重建,如果子樹是一個復雜度很高的樹,可以使用GlobalKey做為子樹widget的key。GlobalKey具有緩存功能

    如何觸發樹更新

    1. 全局更新:調用runApp(rootWidget),一般flutter啟動時調用后不再會調用
    2. 局部子樹更新, 將該子樹做StatefullWidget的一個子widget,并創建對應的State類實例,通過調用state.setState() 觸發該子樹的刷新

    Widget

    StatefullWidget vs StatelessWidget

    1. StatelessWidget:無中間狀態變化的widget,需要更新展示內容就得通過重新new,flutter推薦盡量使用StatelessWidget
    2. StatefullWidget:存在中間狀態變化,那么問題來了,widget不是都immutable的,狀態變化存儲在哪里?flutter 引入state的類用于存放中間態,通過調用state.setState()進行此節點及以下的整個子樹更新

    State 生命周期

    1. initState(): state create之后被insert到tree時調用的
    2. didUpdateWidget(newWidget):祖先節點rebuild widget時調用
    3. deactivate():widget被remove的時候調用,一個widget從tree中remove掉,可以在dispose接口被調用前,重新instert到一個新tree中
    4. didChangeDependencies():
      • 初始化時,在initState()之后立刻調用
      • 當依賴的InheritedWidget rebuild,會觸發此接口被調用
    5. build():
      • After calling [initState].
      • After calling [didUpdateWidget].
      • After receiving a call to [setState].
      • After a dependency of this [State] object changes (e.g., an[InheritedWidget] referenced by the previous [build] changes).
      • After calling [deactivate] and then reinserting the [State] object into the tree at another location.
    6. dispose():Widget徹底銷毀時調用
    7. reassemble(): hot reload調用

    注意事項:

    1. A頁面push一個新的頁面B,A頁面的widget樹中的所有state會依次調用deactivate(), didUpdateWidget(newWidget)、build()(這里懷疑是bug,A頁面push一個新頁面,理論上并沒有將A頁面進行remove操作),當然從功能上,沒有看出來有什么異常
    2. 當ListView中的item滾動出可顯示區域的時候,item會被從樹中remove掉,此item子樹中所有的state都會被dispose,state記錄的數據都會銷毀,item滾動回可顯示區域時,會重新創建全新的state、element、renderobject
    3. 使用hot reload功能時,要特別注意state實例是沒有重新創建的,如果該state中存在一下復雜的資源更新需要重新加載才能生效,那么需要在reassemble()添加處理,不然當你使用hot reload時候可能會出現一些意想不到的結果,例如,要將顯示本地文件的內容到屏幕上,當你開發過程中,替換了文件中的內容,但是hot reload沒有觸發重新讀取文件內容,頁面顯示還是原來的舊內容

    數據流轉

    從上往下

    數據從根往下傳數據,常規做法是一層層往下,當深度變大,數據的傳輸變的困難,flutter提供InheritedWidget用于子節點向祖先節點獲取數據的機制,如下例子:

    class FrogColor extends InheritedWidget {const FrogColor({Key key,@required this.color,@required Widget child,}) : assert(color != null), assert(child != null), super(key: key, child: child); final Color color; static FrogColor of(BuildContext context) {return context.inheritFromWidgetOfExactType(FrogColor);} @overridebool updateShouldNotify(FrogColor old) => color != old.color; } 

    child及其以下的節點可以通過調用下面的接口讀取color數據
    FrogColor.of(context).color

    說明:BuildContext 就是Element的一個接口類

    image

    context.inheritFromWidgetOfExactType(FrogColor)其實是通過context/element往上遍歷樹,查找到第一個FrogColor的祖先節點,取該節點的widget對象

    從下往上

    子節點狀態變更,向上上報通過發送通知的方式

    • 定義通知類,繼承至Notification
    • 父節點使用NotificationListener 進行監聽捕獲通知
    • 子節點有數據變更調用下面接口進行數據上報
      Notification(data).dispatch(context)

    閑魚flutter的界面框架設計

    image

    Layout

    Size 計算

    image

    parent傳入約束條件,在dramframe的layout階段,child根據自身的渲染內容返回size

    問題:在build()階段獲取不到size,很多時候需要提前知道部分widget size來進行布局,解決方案當widget 在對應renderobject的layout階段之后,發送一個LayoutChangeNotification,參考SizeChangedLayoutNotifier class,但是SizeChangedLayoutNotifier沒有上報init layout size,可以自己參考這個實現封裝一個Notifier

    Offset 計算

    1. renderObject拿到計算好的size,再加上一些布局屬性(align、paddig)等,計算child相對parent的offset
    2. offset存放在每個child renderObject的BoxParentData中
    3. 當parent擁有mutil children時,BoxParentData還用來存children兄弟節點之間的遍歷順序

    Relayout boundary

    renderObject在layout階段做了Relayout boundary的優化,當子樹進行relayout時,滿足下面三種中的一種

    • parentUsesSize == false
    • sizedByParent == true
    • constraints.isTight

    那么該renderObject設置為Relayout boundary,也就是該renderObject的重新layout不觸發parent的layout,一般情況下開發人員不需要關心Relayout boundary,除非是使用CustomMultiChildLayout

    Paint

    Layer

    iOS的每一個UIView都有一個layer,flutter的render object不一定存在layer,一般情況下一個renderObject子樹都渲染在一個layer上,那么什么renderObject具有layer,子renderObject怎么渲染到這個layer?

    1. 當一個renderObject的 alwaysNeedsCompositing == true 或者isRepaintBoundary == true,renderOject會有對應的compositing layer
    2. 子renderObject會對目標layer返回對應的offsetLayer, 目標compositing layer再根據offset合成一個渲染的紋理buffer


      image

    Repaint Boundary

    類似Relayout boundary,Paint階段也有Repaint Boundary,目的和layout一樣,就是對應子樹的paint不會導致外部的repaint,但是Relayout boundary需要開發人員自己設置,使用RepaintBoundary widget進行設置,ListView在渲染的item默認都是使用了RepaintBoundary,顯而易見ListView的children之間都是相互獨立的。
    Flutter建議復雜的image渲染使用RepaintBoundary,image的渲染需要io操作,然后解碼,最后渲染,使用RepaintBoundary可以進行gpu的緩存,但是不一定就會緩存,engine會判斷這個image是否足夠復雜,畢竟gpu緩存還是非常珍貴的,同時RepaintBoundary還會對一些反復渲染的layer進行緩存處理(反復渲染3次及以上,這個是flutter的視頻中提到的)

    結語

    Flutter還處于Beta階段,有些界面編程的接口設計還不夠成熟,相比iOS和安卓生態還很不成熟,需要我們共同的創建,Flutter提供的調試工具相比一開始接觸的時候,已經完善很多,讓我們給Flutter更多的耐心和包容,期待Flutter越來越完善。
    簡歷投遞:guicai.gxy@alibaba-inc.com

    參考資料

    • https://github.com/flutter/flutter
    • https://github.com/flutter/engine
    • https://flutter.io/

    本頁內容由塔燈網絡科技有限公司通過網絡收集編輯所得,所有資料僅供用戶學習參考,本站不擁有所有權,如您認為本網頁中由涉嫌抄襲的內容,請及時與我們聯系,并提供相關證據,工作人員會在5工作日內聯系您,一經查實,本站立刻刪除侵權內容。本文鏈接:http://www.juherenli.com/18147.html
    相關APP開發
     八年  行業經驗

    多一份參考,總有益處

    聯系深圳網站公司塔燈網絡,免費獲得網站建設方案及報價

    咨詢相關問題或預約面談,可以通過以下方式與我們聯系

    業務熱線:余經理:13699882642

    Copyright ? 2013-2018 Tadeng NetWork Technology Co., LTD. All Rights Reserved.    

    青青青亚洲精品国产| 久久乐国产精品亚洲综合| 久久午夜无码鲁丝片直播午夜精品| 日本精品在线观看视频| 精品无码一区在线观看| 日韩精品在线观看| 久青草中文字幕精品视频| 伊人久99久女女视频精品免| 久久精品国产清高在天天线| 自拍偷在线精品自拍偷无码专区| 国产精品9999久久久久仙踪林| 国产四虎免费精品视频| 99国产精品自在自在久久| 国产啪精品视频网站免费尤物| jazzjazz国产精品| 国产精品久久二区二区| 国产精品夜夜春夜夜爽久久小 | 一本色道久久综合亚洲精品| 国产在线精品观看一区| 图片区精品综合自拍| 亚洲精品亚洲人成在线| 久久机热这里只有精品无需| 久久久久人妻一区精品色| 国产suv精品一区二区33| 中文无码久久精品| 日韩精品无码一区二区三区四区| 精品无码av无码专区| 国产大片91精品免费观看男同| 欧洲精品免费一区二区三区| 国产精品成熟老妇女| AV天堂午夜精品一区二区三区| 五月天精品视频在线观看| 思思久久好好热精品国产| 色综合久久夜色精品国产| 999国内精品永久免费观看| 国产精品福利一区二区| 亚洲精品亚洲人成在线麻豆| 青青青国产精品一区二区| 久久香综合精品久久伊人| 91精品国产手机| 国产精品美女久久久久网|