在介面上顯示一個寬為 300、高為 200 矩形, 如下圖所示
前端實現方式很多 (1)dom
<html>
<style type='text/css'>
.demo {
width: 300px;
height: 200px;
background: #f00;
}
</style>
<body>
<div class='demo'></div>
</body>
</html>
複製程式碼
html文字描述了頁面應該有哪些功能,css告訴瀏覽器該長什麼樣 瀏覽器引擎通過解析html和css,翻譯成一些列的預定義UI控制元件 然後UI控制元件去呼叫作業系統繪圖指令去繪製影象展現給使用者
(2) canvas
<html>
<body>
<script>
var c= document.createElement("canvas");
var ctx=c.getContext("2d");
ctx.fillStyle="#FF0000";
ctx.fillRect(0,0,300,200);
document.body.append(c);
</script>
</body>
</html>
複製程式碼
可以通過畫布繪製,如以上程式碼所示, 通過建立 canvas 節點,獲取 canvas 繪製上下文,呼叫相關 API, 便可繪製一個矩形,並 append 到 body 節點中。
1、圖形繪製原理
顯示器(螢幕)是由一個個物理顯示單元(畫素點)組成,而每一個畫素點可以發出多種顏色,顯示器成相的原理就是在不同的物理畫素點上顯示不同的顏色,最終構成完整的影象。圖形計算和繪製都是由相應的硬體來完成,作業系統一般遮蔽了這些底層硬體操作指令,提供一些封裝後的API供作業系統之上的應用呼叫,但是對於應用開發者來說,直接呼叫這些作業系統提供的API是比較複雜和低效的,因此幾乎所有用於開發GUI程式的程式語言都會在作業系統之上再封裝一層,將作業系統原生API封裝在一個程式設計框架和模型中,然後定義一種簡單的開發規則來開發GUI應用程式,而這一層抽象,就是所謂的“UI”系統,如Android SDK正是封裝了Android作業系統API,提供了一個“UI描述檔案XML+Java操作DOM”的UI系統,而iOS的UIKit 對View的抽象也是一樣的,他們都將作業系統API抽象成一個基礎物件(如用於2D圖形繪製的Canvas),然後再定義一套規則來描述UI,如UI樹結構,UI操作的單執行緒原則等。
2、跨端應用
Weex
上圖是weex 工作流程圖,WEEX框架中核心的部分是JavaScript Runtime。當JSBundle從伺服器端下載完成之後,WEEX在Android、iOS和Web端會執行一個JavaScript引擎來執行JSBundle,同時向各終端的渲染層傳送渲染指令,並排程客戶端的渲染引擎實現檢視渲染、事件繫結和處理使用者互動等操作。
ReactNative
上圖為 reactnative 流程架構圖,和 weex 類似, reactNative 所有的標籤也不是真實控制元件,JS 程式碼中所生成存的 dom,最後都是由 Native 端解析,再得到對應的Native控制元件渲染。
不管是 Weex 還是 ReactJS,自身是不直接繪製UI的,而是呼叫原生元件執行頁面渲染操作。Bridges則是用來繪製指令給原生元件進行繪製
Flutter
與 React Native 和 WEEX 使用原生元件渲染介面不同,Flutter並不需要使用原生元件來渲染介面,而是使用自帶的渲染引擎(Engine層)來繪製頁面元件。
如下圖所示,Flutter框架主要由 Framework 層和 Engine 層組成,其中 Engine 層 其以Skia作為其2D渲染引擎,有自己的UI繪製機制。
在 Flutter 中,也有 Canvas 的概念,Engine 層向 Dart 層的暴露了 Canvas, PictureRecorder 等介面,利用它們可以繪製任何自己想顯示的東西~如以下程式碼,類似 js 中呼叫 canvas 類似
void main() {
// 獲取繪製圖層
OffsetLayer rootLayer = new OffsetLayer();
PictureLayer pictureLayer = PictureLayer(Rect.zero);
rootLayer.append(pictureLayer);
// 繪製圖形
pictureLayer.picture =
createSolidRectanglePicture(Color(0xFFFF0000), 300, 200);
rootLayer.updateSubtreeNeedsAddToScene();
SceneBuilder sceneBuilder = SceneBuilder();
rootLayer.addToScene(sceneBuilder);
Scene scene = sceneBuilder.build();
window.onDrawFrame = () {
// 將繪製的圖層輸送到GPU
window.render(scene);
};
window.scheduleFrame();
}
複製程式碼
簡單地講,flutter 提供一張畫布、我們可以在畫布上天馬星空,繪製任何自己想繪製的東西
3、Vsync 更新機制
Vsync 相關概念
在計算機系統中,CPU、GPU和顯示器以一種特定的方式協作:CPU將計算好的顯示內容提交給 GPU,GPU渲染後放入幀緩衝區,它們是影象生產者,往幀緩衝區(BufferQueue) 不斷填充資料, 顯示器可以理解為消費者,從幀緩衝區取幀資料(BufferQueue)中獲取資料, 負責把渲染後的內容呈現到螢幕上。
為了更新顯示畫面,顯示器是以固定的頻率重新整理(從GPU取資料),比如有一部手機螢幕的重新整理頻率是 60Hz。當一幀影象繪製完畢後準備繪製下一幀時,顯示器會發出一個垂直同步訊號(如VSync), 60Hz的螢幕就會一秒內發出 60次這樣的訊號, 這個訊號是用來同步 CPU、GPU 和顯示器的工作的,即提示 CPU 和 GPU 進行下一幀工作的訊號, Android系統每隔16.6ms發出VSYNC訊號,來通知介面進行輸入、動畫、繪製等動作
為什麼需要 Vsync
Refresh Rate:代表了螢幕在一秒內重新整理螢幕的次數,這取決於硬體的固定引數,例如60Hz Frame Rate:代表了GPU在一秒內繪製操作的幀數,例如30fps,60fps
如果沒有 Vsync 機制,容易產生以下兩種情況
(1)供過於求 現在的顯示卡通常可以將CS的幀率渲染到120以上,即120FPS。可通常我們使用的顯示器只能達到60HZ的重新整理率。顯然,即使顯示卡在1秒內將畫面變化了120次,但顯示器只有展示其中60幅的能力。這種時候,我們感知的流暢度其實是60FPS,顯示卡在顯示器兩次重新整理的一個週期內,輸出了兩幀畫面,前一幀就會被當作無效幀丟棄、多餘的幀是無效幀,對畫面效果提升沒有任何作用。
(2)供不應求
如果影象生成的速率低於影象消費速率,即供不應求的情況下,容易產生卡頓的現象,如上圖中:
第一個 16ms:Display顯示第0幀,CPU處理完第一幀後,GPU緊接其後處理繼續第一幀
第二個16ms:因為早在上一個16ms時間內,第1幀已經由CPU,GPU處理完畢。故Display可以直接顯示第1幀。顯示沒有問題。但在本16ms期間,CPU和GPU卻並未及時去繪製第2幀資料,而是在本週期快結束時,CPU/GPU才去處理第2幀資料
第三個16ms: 此時Display應該顯示第2幀資料,但由於CPU和GPU還沒有處理完第2幀資料,故Display只能繼續顯示第一幀的資料,結果使得第1幀多畫了一次
Vsync 機制是起到一個協調作用,顯示卡在渲染每一幀之前會等待垂直同步訊號,只有顯示器完成了一次重新整理時,發出垂直同步訊號,顯示卡才會渲染下一幀,確保重新整理率和幀率保持同步,以達到供需平衡的效果,防止卡頓現象
Flutter Vsync 流程