色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Flutter Web有什么不同之處

谷歌開發(fā)者 ? 來源:GSYTech ? 作者:戀貓de小郭 ? 2022-07-08 09:51 ? 次閱讀

Flutter Web 穩(wěn)定版本發(fā)布至今也有一年多了,經過這一年多的發(fā)展,今天就讓我們來看看 Flutter Web 究竟有什么不同之處,本篇分享主要內容是目前 Flutter 下少有較為全面的 Web 內容。

一、起源與實現(xiàn)

說起 Flutter 的起源就很有意思,大家都知道早期 Flutter 最先支持的平臺是 AndroidiOS,至今最核心的維護平臺依然是 Android 和 iOS,但是事實上 Flutter 其實起源于前端團隊。

Flutter 來源于前端 Chrome 團隊,起初 Flutter 的創(chuàng)始人和整個團隊幾乎都是來自 Web,在 Flutter 負責人 Eric 的相關訪談中,Eric 表示 Flutter 來自 Chrome 內部的一個實驗,他們把一些亂七八糟的 Web 規(guī)范去掉后,在一些內部基準測試的性能居然能提升 20 倍,因此 Google 內部就開始立項,所以 Flutter 出現(xiàn)了。

另外前端的同學應該知道,Dart 起初也是為了 Web 而生,事實上 Dart 誕生至今也有 10 年了,所以可以說 Flutter 其實充滿了 Web 的基因。

但是作為從 Web 里誕生的框架,和 React Native/ Weex 不同的是,前者是先有了 Web 下的 React 和 Vue 實現(xiàn)之后才有的客戶端支持,而對于 Flutter 則是反過來,先有客戶端實現(xiàn)之后才支持 Web 平臺,這里其實可以和 Weex 做個簡單對照。

Weex 作為曾經閃耀過的跨平臺框架,它同樣支持 Android、iOS 和 Web 三個平臺,在 Android 和 iOS 上 Weex 和 React Native 差異性不大,在 Web 上 Weex 則是刪減版的 Vue 支持,而由于 API 和平臺差異性的問題,Weex 在 Web 上的支持體驗一直不是很好:

因為 Weex 需要依賴平臺控件實現(xiàn)渲染,導致一個 Text 控件需要兼顧 Android、iOS 和 Web 上原生平臺接口的邏輯,從而出現(xiàn)各種由于耦合帶來的兼容性問題。

而 Flutter 實現(xiàn)更為特別,通過 Skia 實現(xiàn)了獨立的渲染引擎之后,在 Android 和 iOS 上控件幾乎就與平臺無關,所以 Flutter 上的控件可以做到獨立且不同平臺上渲染一致的效果。

d62c4d6e-fe5e-11ec-ba43-dac502259ad0.png

但是回到 Web 上又有些特殊,首先 Web 平臺完全是 html / js / css 的天下,并且 Web 平臺需要同時兼顧 PC 和 Mobile 的不同環(huán)境,這就讓 Flutter Web 成了 Flutter 所有平臺里 “最另類又奇葩” 的落地。

d63caf38-fe5e-11ec-ba43-dac502259ad0.png

首先 Flutter Web 和其他 Flutter 平臺一樣共用一套 Framework,理論上絕大多數(shù)的控件實現(xiàn)都是通用的,當然如果要說最不兼容的 API 對象,那肯定就是 Canvas 了,這其實和 Flutter Web 特殊的實現(xiàn)有關系,后面我們會聊到這個問題。

而由于 Web 的特殊場景,F(xiàn)lutter Web 在 “幾經周折” 之后落地了兩種不同的渲染邏輯: html 和 canvaskit,它們的不同之處在于:

html

好處: html 的實現(xiàn)更輕量級,渲染實現(xiàn)基本依賴于 Web 平臺的各種 HTMLElement,特別是 Flutter Web 下定義的各種 《flt-*》 實現(xiàn),可以說它更貼近現(xiàn)在的 Web 環(huán)境,所以有時候我們也稱呼它為 DomCanvas,當然隨著 Flutter Web 的發(fā)展這個稱呼也發(fā)生了一些變化,后續(xù)我們會詳細講到這個。

問題: html 的問題也在于太過于貼近 Web 平臺,這就和 Weex 一樣,貼近平臺也就是耦合于平臺,事實上 DomCanvas 實現(xiàn)理念其實和 Flutter 并不貼切,也導致了 Flutter Web 的一些渲染效果在 html 模式下存在兼容問題,特別是 Canvas 的 API。

canvaskit

好處: canvaskit 的實現(xiàn)可以說是更貼近 Flutter 理念,因為它其實就是 Skia + WebAssembly 的實現(xiàn)邏輯,能和其他平臺的實現(xiàn)更一致,性能更好,比如滾動列表的渲染流暢度更高等。

問題: 很明顯使用 WebAssembly 帶來的 wasm 文件會導致體積增大不少,Web 場景下其實很講究加載速度,而在這方面 wasm 能優(yōu)化的空間很小,并且 WebAssembly 在兼容上也是相對較差,另外 skia 還需要自帶字體庫等問題都挺讓人頭痛。

默認情況下 Flutter Web 在打包渲染時會把 html 和 canvaskit 都打包進去,然后在 PC 端使用 canvaskit 模式,在 mobile 端使用 html 模式,當然您也可以在打包時通過 flutter build web --web-renderer html --release 之類的配置強行指定渲染模式。

既然這里我們講到了 Flutter Web 的打包構建,那就讓我們先從構建打包角度開始來深入介紹 Flutter Web。

二、構建和優(yōu)化

Flutter Web 雖說是和其他平臺共用一個 framework,但是它在 dart 層開始就有一套自己特殊的 engine 實現(xiàn),并且這套實現(xiàn)是獨立于 framework 的一套特殊代碼。

所以在 Flutter Web 打包時,會把默認的 /flutter/bin/cache/lib/_engine 變成了 flutter/bin/cache/flutter_web_sdk/lib/_engine 的相關實現(xiàn),這是因為 Flutter Web 在 framework 之下的 engine 需要一套特殊的 API。

下圖右側構建是指定 web 的打包路徑,和左邊默認時的對比。

d653a300-fe5e-11ec-ba43-dac502259ad0.png

同樣下圖所示,可以看到 web sdk 里會有如 html、canvaskit 這樣不同的實現(xiàn),甚至會有一個特殊的 text 目錄,這是因為在 web 上對于文本的支持是個十分復雜的問題。

d65eed1e-fe5e-11ec-ba43-dac502259ad0.png

那到這里我們知道了在 _engine 層面,F(xiàn)lutter Web 有著自己一套獨立的實現(xiàn),那構建之后的產物是什么樣的情況呢?

如下圖所示是 GSY 的一個簡單的開源示例項目,在部署到服務器后可以看到,默認情況下在不做任何處理時,在 PC 端打開后會使用 canvaskit 渲染,主要會有:

2.3 MB 的 main.dart.js;

2.8 MB 的 canvaskit.wasm;

1.5 MB 的 MaterialIcons-Regular.otf;

284 kB 的 CupertinoIcons.ttf。

d6841cc4-fe5e-11ec-ba43-dac502259ad0.png

可以看到這些文件占據了 Flutter Web 編譯后產物的大部分體積,并且從大小上看確實讓人有些無法接受,因為示例項目的代碼量并不大,結構也不復雜,這樣的體積肯定十分影響加載速度。

所以我們首先考慮在 html 和 canvaskit 兩種渲染模式中先選定一種,出于實用性考慮,結合前面的對比情況,選用 html 渲染模式在兼容性和可優(yōu)化上會更友好,所以這里優(yōu)化的第一步就是先指定 html 模式作為渲染引擎。

開始優(yōu)化

首先可以看到 CupertinoIcons.ttf 這個矢量圖標文件,雖然默認創(chuàng)建項目時會通過 cupertino_icons 被添加到項目里,但是由于我們不需要使用,所以可以在 yaml 文件里去除。

之后通過運行 flutter build web --release --web-renderer html 后,可以看到使用 html 模式加載后的產物很干凈,而需要優(yōu)化的體積現(xiàn)在主要在 main.dart.js 和 MaterialIcons-Regular.otf 上。

d6939550-fe5e-11ec-ba43-dac502259ad0.png

雖然在項目中我們會使用到 MaterialIcons 的一些矢量圖標,但是每次加載都要全量加載一個 1.5 MB 的字體庫文件顯然并不符合邏輯,所以在 Flutter 里官方提供了 --tree-shake-icons 的命令幫助我們優(yōu)化這部分的內容。

但是不幸的是,如下圖所示,在當前的 2.10 版本下該配置運行會有 bug,而不幸中的萬幸是,在原生平臺的編譯中 shake-icons 行為是可以正常執(zhí)行。

d6a67df0-fe5e-11ec-ba43-dac502259ad0.png

所以我們可以先運行 flutter build apk,然后通過如下命令,將 Android 上已經 shake-icons 的 MaterialIcons-Regular.otf 資源復制到已經編譯好的 web/ 目錄下。

cp -r 。/build/app/intermediates/flutter/release/flutter_assets/ 。/build/web/assets

再次打包后可以看到,經過優(yōu)化后 MaterialIcons-Regular.otf 資源如今只剩下 3.2 kB,那接下來就是考慮針對 2.2 MB 的 main.dart.js 進行優(yōu)化處理。

d6af327e-fe5e-11ec-ba43-dac502259ad0.png

要優(yōu)化 main.dart.js,我們就要講到 Flutter 里的 deferred-components,在 Flutter 里可以通過把控件定義為 “deferred component” 來實現(xiàn)控件的懶加載,而這個行為在 Flutter Web 上被編譯之后就會變成多個 *part.js 文本,原理上就是對 main.dart.js 進行拆包。

舉個例子,首先我們定義一個普通的 Flutter 控件,按照正常的控件進行實現(xiàn)就可以。

import ‘package:flutter/widgets.dart’;class DeferredBox extends StatelessWidget { DeferredBox() {} @override Widget build(BuildContext context) { return Container( height: 30, width: 30, color: Colors.blue, ); }}

在需要的地方 import 對應控件然后添加 deferred as box 關鍵字,之后在適當時機通過 box.loadLibrary() 加載控件,最后通過 box.DeferredBox() 渲染。

import ‘box.dart’ deferred as box;class MainPage extends StatefulWidget { @override _MainPageState createState() =》 _MainPageState();}class _MainPageState extends State《MainPage》 { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return FutureBuilder《void》( future: box.loadLibrary(), builder: (BuildContext context, AsyncSnapshot《void》 snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasError) { return Text(‘Error: ${snapshot.error}’); } return box.DeferredBox(); } return CircularProgressIndicator(); }, ); }}

當然,這里還需要額外在 ymal 文件里添加 deferred-components 來制定對應的 libraries 路徑。

deferred-components: - name: crane libraries: - package:gsy_flutter_demo/widget/box.dart

回歸到上面的 GSY 示例項目中,通過相對極端的分包實現(xiàn),這里把 GSY 示例里的每個頁面都變成一個獨立的懶加載頁面,然后在頁面跳轉時再加載顯示,最終打包部署后如下圖所示:

d6bc08b4-fe5e-11ec-ba43-dac502259ad0.png

可以看到拆分之后 main.dart.js 從 2.2 MB 變成了 1.6 MB,而其他內容通過 deferred components 變成了各個 part.js 的獨立文件,并且只在點擊時才動態(tài)下載對應的 part.js 文件,但是此時的 main.dart.js 依舊不小,而官方提供的能力上已經沒有太多優(yōu)化的余地。

在這里可以通過前端的 source-map-explorer 工具去分析這個文件,首先在編譯時要添加 --source-maps 命令,這樣在打包時會生成 main.dart.js 的 source map 文件,然后就執(zhí)行 source-map-explorer main.dart.js --no-border-checks 生成對應的分析圖:

d6cf05d6-fe5e-11ec-ba43-dac502259ad0.png

這里只展示能夠被 mapped 的部分,可以看到 700k 幾乎就是 Flutter Web 整個 framewok + engine + vm 的大小,而這部分內容其實可以優(yōu)化的空間并不大,盡管會有一些如 kIsWeb 的冗余代碼,但是其實可以調整的內容并不多,大概有 36 處可以調整和刪減的地方,實質上打包時 Flutter Web 也都有相應的優(yōu)化壓縮處理,所以這部分收益并不高。

d6e2d9ee-fe5e-11ec-ba43-dac502259ad0.png

另外,如下圖所示是兩種不同 web rendder 構建后代碼上的差異,可以看到 html 和 canvaskit 單獨構建后的 engine 代碼結構差異性還是很大的。

d6f0defe-fe5e-11ec-ba43-dac502259ad0.png

而如果您在編譯時默認的 auto 模式,就會看到 html 和 canvaskit 的代碼都會打包進去,所以相對的 main.dart.js 也會增加一些。

d700aa46-fe5e-11ec-ba43-dac502259ad0.png

那還有什么可以優(yōu)化的地方嗎?還是有的,通過外部手段,例如通過在部署時開啟 gzip 或者 brotli 壓縮,如下圖所示,開始 gzip 后大概可以讓 main.dart.js 下降到 400k 左右。

d713c2a2-fe5e-11ec-ba43-dac502259ad0.png

另外也有在 index.html 里增加 loading 效果來做等待加載過程的展示,例如:

《!DOCTYPE html》《html》《head》 《meta charset=“UTF-8”》 《title》gsy_flutter_demo《/title》 《style》 .loading { display: flex; justify-content: center; align-items: center; margin: 0; position: absolute; top: 50%; left: 50%; -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%); }

.loader { border: 16px solid #f3f3f3; border-radius: 50%; border: 15px solid ; border-top: 16px solid blue; border-right: 16px solid white; border-bottom: 16px solid blue; border-left: 16px solid white; width: 120px; height: 120px; -webkit-animation: spin 2s linear infinite; animation: spin 2s linear infinite; }

@-webkit-keyframes spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); } }

@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }《/style》《/head》《body》 《div class=“l(fā)oading”》 《div class=“l(fā)oader”》《/div》 《/div》 《script src=“main.dart.js” type=“application/javascript”》《/script》《/body》《/html》

所以大致上以上這些就是今天關于 Flutter Web 上產物體積的優(yōu)化,總結起來就是:

去除無用的 icon 引用;

使用 tree-shake-icons 優(yōu)化引用矢量圖庫;

通過 deferred-components 實現(xiàn)懶加載分包;

開啟 gzip 等壓縮算法壓縮 main.dart.js。

三、渲染

講完構建,最后我們聊聊渲染,F(xiàn)lutter Web 的渲染在 Flutter 里是十分特殊的,前面我們說過它自帶了兩種渲染模式,而我們知道 Flutter 的設計理念里,所有的控件都是通過 Engine 繪制出來的,如果這時候您去 framework 里看 Canvas 的實現(xiàn),就會發(fā)現(xiàn)它其實繼承的是 NativeFieldWrapperClass1:

d71ed1f6-fe5e-11ec-ba43-dac502259ad0.png

NativeFieldWrapperClass1 也就是它的邏輯是由不同平臺的 Engine 區(qū)分實現(xiàn),其中編譯后的 Flutter Web 上的 Canvas 代碼應該是繼承如下所示的結構:

d73d47e4-fe5e-11ec-ba43-dac502259ad0.png

可以看到在 Flutter Web 的 Canvas 里會根據邏輯判斷是使用 CanvasKitCanvas 還是 SurfaceCanvas,而相對于直接使用 skia 的 CanvasKitCanvas,更貼近 Web 平臺的 SurfaceCanvas 在實現(xiàn)的耦合復雜度上會更高。

首先如下圖所示是 Flutter Web 里 Canvas 的大致結構,而接下來我們要聊的主要也是集中在 SurfaceCanvas 上,為什么 SurfaceCanvas 層級會這么復雜,它們又是怎么分配繪制,接下來就讓我們深入揭秘它們的規(guī)則。

d753e698-fe5e-11ec-ba43-dac502259ad0.png

先看例子,如下圖所示,可以看到在 html 渲染模式下,F(xiàn)lutter Web 是有一大堆自定義的 《flt-*》 標簽實現(xiàn)渲染,并且在一個長列表中,標簽會被控制在一個合適的數(shù)量,在滾動時進行動態(tài)切換渲染。

d75f1d60-fe5e-11ec-ba43-dac502259ad0.jpg

如果這時候我們放慢去看細節(jié),如下動圖所示,可以看到當 item 處于不可見時 《flt-picture》 里其實并沒有內容,而當 Item 可見之后,《flt-picture》 下會有 《canvas》 標簽把文字繪制出來。

d77223f6-fe5e-11ec-ba43-dac502259ad0.gif

看到一個重點沒有?在這里的文本為什么是由 《canvas》 標簽繪制而不是 《p》 標簽之類的呢?這就是我們重點要講的 SurfaceCanvas 渲染邏輯。

在 Flutter Web 的 SurfaceCanvas 里,文本繪制一般都會是以這樣的情況出現(xiàn),基本都是從 picture 開始進入繪制流程:

d7cb13bc-fe5e-11ec-ba43-dac502259ad0.png

那么在對應的 picture.dart 的代碼實現(xiàn)里可以看到,如下關鍵代碼所示,當 hasArbitraryPaint 為 true 時就會進入到 BitmapCanvas 的邏輯,不然就會使用 DomCanvas。

void applyPaint(EngineCanvas? oldCanvas) { if (picture.recordingCanvas!.renderStrategy.hasArbitraryPaint) { _applyBitmapPaint(oldCanvas); } else { _applyDomPaint(oldCanvas); }}

那么這里有兩個問題: BitmapCanvas 和 DomCanvas 的區(qū)別是什么?hasArbitraryPaint 的判斷邏輯是什么?

首先 BitmapCanvas 和 DomCanvas 的最大的區(qū)別就是:

DomCanvas 會通過創(chuàng)建標簽來實現(xiàn)繪制,比如文本利用 p + span 標簽進行渲染;

BitmapCanvas 會考慮優(yōu)先使用 canvas 渲染,如果場景需要再使用標簽來實現(xiàn)繪制。

在 web sdk 里 hasArbitraryPaint 參數(shù)默認是 false,但是在需要執(zhí)行以下這些行為時就會被設置為 true,而這些調用上可以看出,其實大部分時候的繪制邏輯是會先進入到 BitmapCanvas 里。

d7db6a5a-fe5e-11ec-ba43-dac502259ad0.png

回到前面的文本問題上,在 Flutter 的文本繪制一般都是通過 drawParagraph 實現(xiàn),所以理論上只要有文本存在,就會進入到 BitmapCanvas 的繪制流程,那么目前看來這個結論符合上面 Item 里文本是使用 canvas 繪制的預期。

那 Flutter 里對于文本,在 BitmapCanvas 又是何時使用 canvas 何時使用 p+span 標簽呢?

我們先看如下代碼,運行后效果如下圖所示,可以看到此時的文本是直接使用 canvas 渲染的,這個結果符合我們目前的預期。

Scaffold( body: Container( alignment: Alignment.center, child: Center( child: Container( child: Text( “v333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333”, ), ), ), ),)

d7ebba22-fe5e-11ec-ba43-dac502259ad0.png

接下來給這段代碼加上一個紅色背景,運行后可以看到,此時的文本變成了 p+span 標簽,并且紅色的背景是通過 draw-rect 標簽實現(xiàn),層級里并沒有 canvas,這又是為什么呢?

Scaffold( body: Container( alignment: Alignment.center, child: Center( child: Container( decoration: BoxDecoration( color: Colors.red, ), child: Text( “v333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333”, ), ), ), ),)

d801c2e0-fe5e-11ec-ba43-dac502259ad0.png

這里就需要先講到 BitmapCanvas 的 drawRect 實現(xiàn),如下關鍵代碼所示,在 drawRect 時,如果在滿足 _useDomForRenderingFillAndStroke 這個函數(shù)條件的情況下,就會通過 buildDrawRectElement 的方式實現(xiàn)渲染,也就是使用 draw-rect 標簽而不是 canvas,所以我們需要先分析這個函數(shù)的判斷邏輯。

@override void drawRect(ui.Rect rect, SurfacePaintData paint) { if (_useDomForRenderingFillAndStroke(paint)) { final html.HtmlElement element = buildDrawRectElement( rect, paint, ‘draw-rect’, _canvasPool.currentTransform); _drawElement( element, ui.Offset( math.min(rect.left, rect.right), math.min(rect.top, rect.bottom)), paint); } else { setUpPaint(paint, rect); _canvasPool.drawRect(rect, paint.style); tearDownPaint(); }}

如下代碼所示,可以看到這個函數(shù)有很多的判斷條件,而得到 true 的條件就是滿足其中三大條件之一即可,下述表格里大致描述了每個條件所代表的意義。

bool _useDomForRenderingFillAndStroke(SurfacePaintData paint) =》 _renderStrategy.isInsideSvgFilterTree || (_preserveImageData == false && _contains3dTransform) || ((_childOverdraw || _renderStrategy.hasImageElements || _renderStrategy.hasParagraphs) && _canvasPool.isEmpty && paint.maskFilter == null && paint.shader == null);

isInsideSvgFilterTree例如有 ShaderMask 或者 ColorFilter 的時候為 true

_preserveImageData一般是在 toImage 的時候才會為 true

_contains3dTransformtransformKind == TransformKind.complex 的時候,也就是矩陣包含縮放、旋轉、z 平移或透視變換

_childOverdraw有 _drawElement 或者 drawImage 的時候,大概就是使用了標簽渲染之后,需要切換畫布

_renderStrategy.hasImageElements有圖片繪制的時候,用 Image 標簽的情況

_renderStrategy.hasParagraphs有文本需要繪制的時候

_canvasPool.isEmpty簡單說就是 canvas == null 的時候

paint.maskFilter == null簡單說就是 Container 等控件沒有配置 shadow 的時候

paint.shader == null簡單說就是 Container 等控件沒有配置 gradient 的時候

大概流程也如圖所示,前面繪制紅色背景時并沒有添加什么特殊配置,所以會進入到 _drawElement 的邏輯,可以看到針對不同的渲染場景,BitmapCanvas 會采取不一樣的繪制邏輯,那為什么前面多了紅色背景就會導致文本也變成標簽呢?

d82c4d62-fe5e-11ec-ba43-dac502259ad0.png

這是因為在 BitmapCanvas 如果有使用標簽構建,也就是 _drawElement 的時候,就會執(zhí)行一個 _closeCurrentCanvas 函數(shù),該函數(shù)會把 _childOverdraw 設置為 true,并且清空 _canvasPool 里的 canvas。

所以我們看 drawParagraph 的實現(xiàn),如下所示代碼,可以看到由于 _childOverdraw 是 true 時,文本會采用 Element 來繪制文本。

@overridevoid drawParagraph(EngineParagraph paragraph, ui.Offset offset) { ···· if (paragraph.drawOnCanvas && _childOverdraw == false && !_renderStrategy.isInsideSvgFilterTree) { paragraph.paint(this, offset); return; } ···· final html.Element paragraphElement = drawParagraphElement(paragraph, offset);

····}

而在 BitmapCanvas 里,有三個操作會觸發(fā) _childOverdraw = true 和 _canvasPool Empty:

_drawElement

drawImage/drawImageRect

drawParagraph

所以先總結一下,結合前面的流程圖,我們可以簡單認為: 在沒有 maskFilter (shadow) 和 shader (gradient) 的情況下,只要觸發(fā)了上述三種情況,就會使用標簽繪制。

是不是感覺有點亂?

不怕,先接著繼續(xù)看新的例子,在原本紅色背景實現(xiàn)的基礎上,這里給 Container 增加了 shadow 用于配置陰影,運行之后可以看到,不管是背景色或者文本又都變成了 canvas 渲染的情況。

Scaffold( body: Container( alignment: Alignment.center, child: Center( child: Container( decoration: BoxDecoration( color: Colors.red, boxShadow: [ BoxShadow( color: Colors.black54, blurRadius: 4.0, offset: Offset(2, 2)) ], ), child: Text( “v333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333”, ), ), ), ), )

d8418eac-fe5e-11ec-ba43-dac502259ad0.png

結合前面的流程看這是符合預期的,因為此時帶有 boxShadow 參數(shù),該參數(shù)會在繪制時通過 toPaint 方法轉化為 maskFilter,所以在 maskFilter != null 的情況下,流程不會進入到 Element 的判斷,所以使用 canvas。

d850dbf0-fe5e-11ec-ba43-dac502259ad0.png

繼續(xù)前面的例子,如果這時候我們再加一個 ColorFiltered 控件,前面表格說過,有 ShaderMask 或者 ColorFilter 的時候,sInsideSvgFilterTree 參數(shù)就會是 true,這時候渲染就會直接進入使用 Element 繪制而無視其他條件如 BoxShadow,從運行結果上看也是如此。

Scaffold( body: Container( alignment: Alignment.center, child: Center( child: ColorFiltered( colorFilter: ColorFilter.mode(Colors.yellow, BlendMode.hue), child:Container( decoration: BoxDecoration( color: Colors.red, boxShadow: [ BoxShadow( color: Colors.black54, blurRadius: 4.0, offset: Offset(2, 2)) ], ), child: Text( “v333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333”, ), ), ), ), ), )

d86108ae-fe5e-11ec-ba43-dac502259ad0.png

可以看到此時變成了兩個 draw-rect 和 p 標簽的繪制,為什么會有這樣的邏輯,因為一些瀏覽器,例如 iOS 設備上的 Safari,它不會把 svg filter 等信息傳遞給 canvas,如果繼續(xù)使用 canvas 就會如 shader mask 等無法正常渲染,詳細可見: #27600。

d8766ce4-fe5e-11ec-ba43-dac502259ad0.png

繼續(xù)這個例子,如果此時不加 ColorFiltered,而是給 Container 添加一個 transform,運行后可以看到還是 draw-rect 和 p 標簽的實現(xiàn),因為此時的 transform 是屬于 TransformKind.complex 的狀態(tài),會導致 _contains3dTransform = true,從而進入 Element 的邏輯。

Scaffold( body: Container( alignment: Alignment.center, child: Center( child: Container( transform: Matrix4.identity()。.setEntry(3, 2, 0.001) 。.rotateX(100)。.rotateY(100), decoration: BoxDecoration( color: Colors.red, boxShadow: [ BoxShadow( color: Colors.black54, blurRadius: 4.0, offset: Offset(2, 2)) ], ), child: Text( “v333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333”, ), ), ), ),)

d8883fa0-fe5e-11ec-ba43-dac502259ad0.png

d89c0620-fe5e-11ec-ba43-dac502259ad0.png

最后再來一個例子,這里回歸到只有紅色背景和陰影的情況,在之前它運行后是使用 canvas 標簽來渲染文本,因為它的 maskFilter != null,但是這時候我們給 Text 配置上 TextDecoratoin,運行之后可以看到背景顏色依然是 canvas,但是文本又變成了 p 標簽的實現(xiàn)。

Scaffold( body: Container( alignment: Alignment.center, child: Center( child: Container( decoration: BoxDecoration( color: Colors.red, boxShadow: [ BoxShadow( color: Colors.black54, blurRadius: 4.0, offset: Offset(2, 2)) ], ), child: Text( “v333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333”, style: TextStyle(decoration: TextDecoration.lineThrough), ), ), ), ), );

d8aafa54-fe5e-11ec-ba43-dac502259ad0.png

這是因為前面說過 drawParagraph,在這個函數(shù)里有另外一個判斷條件 _drawOnCanvas,在 Flutter Web 繪制文本時,當文本具備不為 none 的 TextDecoration 或者 fontFeatures 時,_drawOnCanvas 就會被設置為 fasle,從而變成使用 p 標簽渲染的情況。

這也很好理解,例如 fontFeatures 是影響字形選擇的參數(shù),如下圖所示,這些行為在 Web 上用 Canvas 繪制相對會麻煩很多。

d8bbf1f6-fe5e-11ec-ba43-dac502259ad0.png

前面講了那么多例子都是 BitmapCanvas,那 Domcanvas 什么時候會用到呢?

還記得前面列舉的方法嗎,需要進入 _applyDomPaint 就需要 hasArbitraryPaint == false,換言之就是沒有文本,然后 drawRect 的時候沒有 shader (radient) 等就可以了。

依然是前面的例子,繪制一個帶有陰影的紅色方框,但是此時把文本內容去掉,運行后可以看到不是 canvas 而是 draw-rect 標簽,因為雖然此時 maskFilter != null (有 shadow),但是因為沒有文本或者 shader (gradient),所以單純普通的 drawRect 并不會觸發(fā) hasArbitraryPaint == true,所以會直接使用 Domcanvas 繪制,完全脫離了 canvas 的渲染。

Scaffold( body: Container( alignment: Alignment.center, child: Center( child: Container( height: 50, decoration: BoxDecoration( color: Colors.red, boxShadow: [ BoxShadow( color: Colors.black54, blurRadius: 4.0, offset: Offset(2, 2)) ], ), ), ), ),)

d8cd51f8-fe5e-11ec-ba43-dac502259ad0.png

所以最后總結一下: 首先除了下圖所示之外的情況,大部分時候 Flutter Web 繪制都會進入到 BitmapCanvas。

d8db99de-fe5e-11ec-ba43-dac502259ad0.png

結合前面介紹的例子,進入到 BitmapCanvas 之后的流程可以總結:

存在 ShaderMask 或者 ColorFilter 就會使用 Element;

一般情況忽略 _preserveImageData,有復雜矩陣變換時也是直接使用 Element,因為復雜矩陣變換 canvas 支持并不好;

_childOverdraw 經常和 _canvasPool.isEmpty 一起達成條件,一般有 picture 上有 _drawElement 之后就會調用 _closeCurrentCanvas 設置 _childOverdraw = true 并且清空 _canvasPool;

結合上述第三個條件的狀態(tài),如果沒有 maskFilter 或者 shader,就會使用 Element 渲染 UI。

d8edc3ca-fe5e-11ec-ba43-dac502259ad0.png

最后針對文本,在 drawParagraph 時還有特殊處理,關于 _childOverdraw 和 !isInsideSvgFilterTree 相關前面解釋過了,新增條件是在有 TextDecoration 或者 FontFeatures 時,也會觸發(fā)文本繪制變?yōu)?Element,也就是 p + span 標簽的形式。

d900c420-fe5e-11ec-ba43-dac502259ad0.png

四、最后

雖然本次介紹的東西不少,但是 Flutter Web 在 html 渲染模式下的知識點遠不止這些,而由小窺大,以 drawRect 和文本為切入點去了解 SurfaceCanvas 就是很不錯的開始。

另外可以看到,在 Flutter Web 里有很多的自定義的 《flt-*》 標簽,這些標簽都是通過如 html.Element.tag(‘flt-canvas’); 等方式創(chuàng)建,它們和 Flutter 里的對應關系如下圖所示,如果感興趣可以在 chrome 的 source 里對應的 dart_sdk.js 查看具體實現(xiàn)。

原文標題:帶您了解最全面的 Flutter Web | 開發(fā)者說·DTalk

文章出處:【微信公眾號:谷歌開發(fā)者】歡迎添加關注!文章轉載請注明出處。

審核編輯:彭靜
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • Android
    +關注

    關注

    12

    文章

    3938

    瀏覽量

    127572
  • API
    API
    +關注

    關注

    2

    文章

    1505

    瀏覽量

    62170
  • iOS
    iOS
    +關注

    關注

    8

    文章

    3395

    瀏覽量

    150743
  • 代碼
    +關注

    關注

    30

    文章

    4802

    瀏覽量

    68745

原文標題:帶您了解最全面的 Flutter Web | 開發(fā)者說·DTalk

文章出處:【微信號:Google_Developers,微信公眾號:谷歌開發(fā)者】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    鴻蒙Flutter實戰(zhàn):14-現(xiàn)有Flutter 項目支持鴻蒙 II

    引言 在之前的文章鴻蒙Flutter實戰(zhàn):09-現(xiàn)有Flutter項目支持鴻蒙中,介紹了如何改造項目,適配鴻蒙平臺。 文中講述了整體的理念和思路,本文更進一步,結合可實操的項目代碼,詳細說明如何實施
    發(fā)表于 12-26 14:59

    CCD傳感器與CMOS傳感器的相同之處不同之處

    ? ? ? ?本文介紹了CCD傳感器與CMOS傳感器的相同之處不同之處。 相對最早發(fā)展起來的模擬相機,數(shù)字相機也是一個很龐大的家族,早在20世紀70年代,相機里出現(xiàn)了以CMOS技術為核心的類型分支
    的頭像 發(fā)表于 11-24 10:39 ?1116次閱讀

    鴻蒙Flutter實戰(zhàn):11-使用 Flutter SDK 3.22.0

    # 使用 Flutter SDK 3.22.0 ## SDK 安裝 參考[鴻蒙Flutter實戰(zhàn):01-搭建開發(fā)環(huán)境]文章的說明,首先安裝 Flutter SDK 3.22.0。 目前鴻蒙化
    發(fā)表于 11-01 15:03

    NXP MCX N23和MCX N94/54的不同之處

    繼2024年一月份發(fā)布了MCXN94/54系列之后,NXP又在6月份發(fā)布了N系列的第二款產品,MCX N23系列,下面小編就為大家揭開它的神秘面紗,來看看這款產品何特點,了解一下它和N94/54又有什么不同之處呢!
    的頭像 發(fā)表于 11-01 12:35 ?461次閱讀
    NXP MCX N23和MCX N94/54的<b class='flag-5'>不同之處</b>

    鴻蒙Flutter實戰(zhàn):09-現(xiàn)有Flutter項目支持鴻蒙

    # 鴻蒙Flutter實戰(zhàn):現(xiàn)有Flutter項目支持鴻蒙 ## 背景 原來使用Flutter開發(fā)的項目,需要適配鴻蒙。 ## 環(huán)境搭建 見文章[鴻蒙Flutter適配指南],
    發(fā)表于 10-23 16:36

    鴻蒙Flutter實戰(zhàn):08-如何調試代碼

    # 鴻蒙Flutter實戰(zhàn):如何調試代碼 ## 1.環(huán)境搭建 參考文章[鴻蒙Flutter實戰(zhàn):01-搭建開發(fā)環(huán)境](https://gitee.com/zacks
    發(fā)表于 10-23 16:29

    鴻蒙Flutter實戰(zhàn):07混合開發(fā)

    # 鴻蒙Flutter實戰(zhàn):混合開發(fā) 鴻蒙Flutter混合開發(fā)主要有兩種形式。 ## 1.基于har 將flutter module打包成har包,在原生鴻蒙項目中,以har包的方式引入
    發(fā)表于 10-23 16:00

    鴻蒙Flutter實戰(zhàn):06-使用ArkTs開發(fā)Flutter鴻蒙插件

    來自 Flutter 的消息調用,分別實現(xiàn)了 \'getPrefs\' 和 \'setPrefs\' 兩個回掉,其中 getPrefs返回值,通過 result.success(val);(見下)異步返回
    發(fā)表于 10-22 21:56

    鴻蒙Flutter實戰(zhàn):04-如何使用DevTools調試Webview

    # 鴻蒙 Flutter 如何使用 DevTools 調試 Webview 在《鴻蒙 Flutter 開發(fā)中集成 Webview》,介紹了如果在 Flutter 中集成 Webview. 本文
    發(fā)表于 10-22 21:53

    繼電器和接觸器什么不同之處?

    繼電器和接觸器是兩種常用的電氣元件,它們在電路中起著非常重要的作用。雖然它們在某些方面有相似之處,但它們之間還是存在一些不同之處。以下是對繼電器和接觸器的詳細比較: 定義和工作原理 繼電器是一種利用
    的頭像 發(fā)表于 06-21 10:10 ?974次閱讀

    智能制造與傳統(tǒng)制造什么不同之處

    、生產方式、管理模式等方面存在很大的不同,這些不同之處正是智能制造的優(yōu)勢和特點。 二、設計理念的不同 傳統(tǒng)制造設計理念 傳統(tǒng)制造的設計理念主要側重于產品的功能性、穩(wěn)定性和成本效益。在設計過程中,設計師需要充分考
    的頭像 發(fā)表于 06-07 15:36 ?3409次閱讀

    單片機和plc什么相同和不同之處

    某些方面具有相似之處,但在許多關鍵方面也存在顯著差異。本文將詳細探討單片機和PLC的相同和不同之處。 一、相同之處 控制功能:單片機和PLC都具有控制功能,可以對各種設備和系統(tǒng)進行控制。它們可以接收輸入信號,處理這些信號,然后輸
    的頭像 發(fā)表于 06-06 14:05 ?1266次閱讀

    AUTOSAR MCAL驅動程序與演示程序中的Libraries中的驅動程序什么不同之處?

    1.關于 AUTOSAR MCAL 驅動程序 與演示程序中的 Libraries 中的驅動程序 什么不同之處? 2.AUTOSAR MCAL 驅動程序中是否包含了 TC397 安全菜單中提及的 SM(安全機制)的接口? 3.是否
    發(fā)表于 05-17 06:55

    淺談兼容 OpenHarmony 的 Flutter

    OpenHarmony SIG 組織在 Gitee 開源了兼容 OpenHarmony 的 Flutter。該組織主要用于孵化 OpenHarmony 相關的開源生態(tài)項目。 ? ? ▲ 倉庫地址
    的頭像 發(fā)表于 02-02 15:22 ?626次閱讀
    淺談兼容 OpenHarmony 的 <b class='flag-5'>Flutter</b>

    光纖和光纜不同之處?

    很多人會有這樣的疑問,光纖和光纜不同之處?主要是因為光纖和光纜這兩個名詞容易引起混淆。在嚴格的定義下,光纖和光纜是兩種不同的東西,然而在現(xiàn)實生活中,許多人仍然會混淆這兩者。為了更好地理解光纖和光纜之間的區(qū)別,我們一起來看一下。
    的頭像 發(fā)表于 01-15 17:01 ?865次閱讀
    主站蜘蛛池模板: 99re在线播放| 国产精品AV视频一二三区| 伊人大香线蕉影院在线播放| 一边喂奶一边做边爱| 在线免费观看a视频| 中文字幕一区二区视频| 99久久热视频只有精品| 超嫩校花被灌醉在线观看| 国产CHINESE HD精品| 国产人妻精品久久久久久很牛 | 青柠在线观看免费播放电影| 日本久久久久久久做爰片日本| 视频专区亚洲欧美日韩| 亚洲精品视频区| 最近中文字幕mv手机免费高清| x8国产精品视频| 国产精品自在拍在线播放| 精品久久久爽爽久久久AV| 免费无码又爽又黄又刺激网站| 日日夜夜狠狠干| 亚洲欧美中文字幕网站大全| 9420高清完整版在线电影免费观看| 芭乐视频免费资源在线观看| 国产午夜精品久久理论片小说| 久久精品热播在线看| 欧美一级成人影院免费的| 亚色九九九全国免费视频| 中文字幕爆乳JULIA女教师| 被爽到叫呻呤视频免费视频| 国产免费午夜| 麻生希快播在线| 四虎永久免费网址| 尤蜜网站在线进入免费| 成人性生交片无码免费看| 狠狠国产欧美在线视频| 欧美一区二区三区久久综| 亚洲薄码区| 99香蕉视频| 狠狠色狠色综合曰曰| 强伦姧久久久久久久久久 | 久啪久久全部视频在线|