一、簡介
為了解決頁面和組件加載緩慢的問題,ArkUI框架引入了動態操作功能,支持組件的預創建,并允許應用在運行時根據實際需求動態加載和渲染組件。
這些動態操作包括動態創建組件(即動態添加組件)和動態卸載組件(即動態刪除組件)。
動態創建組件是指在build生命周期之外提前創建組件,這種方式不僅能節省組件創建時間,提升用戶體驗,還能將獨立邏輯封裝起來,助力應用的模塊化開發。
而動態卸載組件則是對動態創建的組件進行卸載和刪除操作。
二、組件預創建原理
在傳統的聲明式開發范式中,組件僅在build階段被創建,開發者無法在其他生命周期階段進行組件創建,這往往會導致頁面加載速度較慢。
然而,ArkUI框架提供的UI動態操作支持組件的預創建,允許開發者在非build生命周期階段提前創建組件。這些預創建的組件可以在頁面加載時直接使用,從而大幅提升頁面的響應速度。
如圖1所示,利用組件預創建機制,可以在動畫執行過程中的空閑時間進行組件預創建和屬性設置。當動畫結束后,再進行屬性和布局的更新,從而節省組件創建時間,加快頁面渲染速度。
圖1組件預創建原理圖?
三、FrameNode自定義節點在動態布局場景下的優勢
1.減少自定義組件創建開銷
在聲明式開發范式中,使用ArkUI的自定義組件對節點樹中的每個節點進行定義時,往往會遇到節點創建效率低下的問題。
這是因為在ArkTS引擎中,每個節點都需要分配內存空間來存儲應用程序的自定義組件和狀態變量,并且在節點創建過程中還需執行組件ID、組件閉包以及狀態變量之間的依賴關系收集等操作。相比之下,使用ArkUI的FrameNode可以避免創建自定義組件對象和狀態變量對象,無需進行依賴收集,從而顯著提升組件創建速度。
2.組件更新更快
在動態布局框架的更新場景中,通常存在一個由樹形數據結構ViewModelA創建的UI組件樹TreeA。當需要使用新的數據結構ViewModelB來更新TreeA時,盡管聲明式開發范式可以實現數據驅動的自動更新,但這一過程中伴隨著大量的diff操作。
對于ArkTS引擎而言,在對一個復雜組件樹(深度超過30層,包含100至200個組件)執行diff算法時,幾乎無法在120Hz的刷新率下保持滿幀運行。然而,使用ArkUI的FrameNode擴展,框架能夠自主掌控更新流程,實現高效的按需剪枝。特別是對于那些僅服務于少數特定業務的動態布局框架,利用這一擴展可以實現快速的更新操作。
3.直接操作組件樹
在聲明式開發范式中,組件樹結構更新操作較為困難。例如,將組件樹中的一個子樹從當前子節點完整移到另一個子節點時,聲明式開發范式無法直接調整組件實例的結構關系,只能通過重新渲染整棵組件樹來實現。
而使用ArkUI的FrameNode擴展,則可以通過操作FrameNode輕松操控該子樹,將其移植到另一個節點,從而實現局部渲染刷新,性能更優。
四、組件動態添加、更新和刪除
1、動態添加組件
動態添加組件的過程包括以下步驟:
-
創建自定義節點。
-
實現NodeController,用于管理自定義節點的創建、顯示、更新等操作,并負責將自定義節點掛載到NodeContainer上。
-
實現NodeController的makeNode方法,該方法會在NodeController實例綁定NodeContainer時被回調,并將返回的節點掛載至NodeContainer。
-
使用NodeContainer顯示自定義節點。
2、創建自定義節點
首先,準備好需要掛載的節點,代碼如下所示:
import { BuilderNode, FrameNode, NodeController } from '@kit.ArkUI';class Params {text: string = 'Hello World';constructor(text: string) {this.text = text;}
}@Builder
function buildText(params: Params) {Column() {Text(params.text).fontSize(50).fontWeight(FontWeight.Bold).margin({bottom: 36})}
}
3、實現NodeController
NodeController是一個抽象類,需要繼承并實現它,代碼如下所示:
class TextNodeController extends NodeController {private textNode: BuilderNode<[Params]> | null = null;private message: string = '';constructor(message: string) {super();this.message = message;}makeNode(context: UIContext): FrameNode | null {return null;}
}
4、實現NodeController的makeNode方法
首先,使用構造函數創建BuilderNode實例。創建BuilderNode對象時,必須傳入對應的UIContext對象。如果BuilderNode作為RenderNode的子節點存在,則需要設置RenderOptions的selfIdealSize屬性。然后,使用BuilderNode的build方法構建組件樹。build()方法需要傳入兩個參數:第一個參數是通過wrapBuilder()封裝的全局@Builder方法;第二個參數是對應的@Builder方法所需的參數對象。如果@Builder方法不帶參數或者存在默認參數,則build()的第二個參數可以省略。
class TextNodeController extends NodeController {private textNode: BuilderNode<[Params]> | null = null;private message: string = '';constructor(message: string) {super();this.message = message;}makeNode(context: UIContext): FrameNode | null {this.textNode = new BuilderNode(context);this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message));return this.textNode.getFrameNode();}
}
5、顯示自定義節點
顯示自定義節點依賴于聲明式渲染容器NodeContainer和對應的控制類NodeController。NodeController的makeNode方法返回的節點會顯示在對應的NodeContainer中。由于makeNode需要返回一個FrameNode,因此如果預期顯示BuilderNode,需要調用BuilderNode的getFrameNode方法來獲取其根節點。詳細代碼如下:
@Entry
@Component
struct Index {@State message: string = "hello";private textNodeController: TextNodeController = new TextNodeController(this.message);build() {Row() {Column() {NodeContainer(this.textNodeController).width('100%').height(100).backgroundColor('#FFF0F0F0')}.width('100%').height('100%')}.height('100%')}
}
6、更新自定義節點
更新自定義節點可以參考BuilderNode的update方法。