開(kāi)發(fā)實(shí)戰(zhàn):從合成大西瓜到真機(jī)運(yùn)行)
這兩年鴻蒙設(shè)備的保有量上來(lái)了身邊不少朋友問(wèn)“能不能用Flutter跑鴻蒙”正好我用Flutter做了一個(gè)合成大西瓜游戲從環(huán)境搭建到上真機(jī)跑通一路踩了不少坑也積累了一些一手經(jīng)驗(yàn)。這篇博文就圍繞“鴻蒙 Flutter 跨平臺(tái)開(kāi)發(fā)”這條主線(xiàn)完整拆解一下合成大西瓜這個(gè)項(xiàng)目從零到一的實(shí)現(xiàn)過(guò)程包括架構(gòu)設(shè)計(jì)、物理引擎選型、核心合成邏輯、鴻蒙端適配、HAP打包和真機(jī)調(diào)試這些環(huán)節(jié)希望能給準(zhǔn)備在鴻蒙上做Flutter開(kāi)發(fā)的同學(xué)一點(diǎn)參考。這不是一篇教程式的“Hello World”而是把整個(gè)項(xiàng)目拆開(kāi)揉碎講清楚每一步為什么要這么選、這么寫(xiě)以及哪些地方在鴻蒙上跟Android/iOS不一樣。內(nèi)容對(duì)剛接觸Flutter的初級(jí)開(kāi)發(fā)者也很友好涉及物理引擎的部分我會(huì)用大白話(huà)解釋不會(huì)上來(lái)就是Box2D公式糊臉。如果你已經(jīng)在用Flutter做業(yè)務(wù)只是想知道鴻蒙適配怎么做可以直接跳到第2章和第5章。1. 項(xiàng)目背景與整體設(shè)計(jì)思路1.1 為什么是“鴻蒙 Flutter”這個(gè)組合合成大西瓜這種游戲單看玩法并不復(fù)雜掉落水果、相同水果碰撞合并、不斷往上疊加超過(guò)警戒線(xiàn)就結(jié)束。但它的核心體驗(yàn)依賴(lài)兩件事物理碰撞的真實(shí)感和水果合并的即時(shí)反饋。如果我用鴻蒙原生去實(shí)現(xiàn)需要自己搞一套物理引擎或者對(duì)接第三方庫(kù)成本并不低如果我用Flutter則可以復(fù)用大量現(xiàn)成的跨平臺(tái)游戲開(kāi)發(fā)方案。Flutter本身是一個(gè)UI框架?chē)?yán)格意義上不是游戲引擎但它的渲染性能足夠應(yīng)付2D休閑游戲。加上Flutter生態(tài)里已經(jīng)有Flame游戲引擎和Forge2D物理引擎合成大西瓜這類(lèi)“水果掉落 碰撞合并”的場(chǎng)景在Flutter里實(shí)現(xiàn)比我預(yù)想中要順利。更關(guān)鍵的是Flutter官方和OpenHarmony社區(qū)這兩年一直在推進(jìn)Flutter對(duì)鴻蒙的適配有一個(gè)相對(duì)成熟的鴻蒙分支可以直接用。這個(gè)組合的核心價(jià)值就是一套Dart代碼同時(shí)覆蓋Android、iOS、鴻蒙甚至后續(xù)可以擴(kuò)展到桌面端對(duì)個(gè)人開(kāi)發(fā)者和中小團(tuán)隊(duì)來(lái)說(shuō)研發(fā)成本確實(shí)能省下來(lái)。注意目前Flutter適配鴻蒙走的不是Flutter官方主干而是OpenHarmony社區(qū)的flutter_flutter分支。這個(gè)分支有對(duì)應(yīng)的版本號(hào)比如3.7.12、3.7.24等不是直接下載flutter官網(wǎng)的穩(wěn)定版就能跑鴻蒙。1.2 合成大西瓜的核心玩法抽象在動(dòng)手寫(xiě)代碼之前我把游戲玩法抽象成了幾個(gè)狀態(tài)和行為水果類(lèi)型不同等級(jí)對(duì)應(yīng)不同水果從葡萄、橘子開(kāi)始一直到大西瓜。掉落行為玩家選擇一個(gè)釋放點(diǎn)水果在重力作用下落到容器內(nèi)與已有水果發(fā)生碰撞。合并規(guī)則兩個(gè)同類(lèi)水果碰撞后消除并生成更高一級(jí)的新水果新水果繼承碰撞位置和速度。結(jié)束判定容器內(nèi)已有水果堆積超過(guò)警戒線(xiàn)且玩家沒(méi)有空間繼續(xù)釋放新水果時(shí)游戲結(jié)束。抽象完以后整個(gè)項(xiàng)目的技術(shù)難點(diǎn)就清晰了物理世界怎么建、碰撞回調(diào)怎么處理、水果尺寸和物理半徑怎么匹配、合并后的新物體如何平滑插入場(chǎng)景。這些都是我在實(shí)現(xiàn)過(guò)程中反復(fù)調(diào)整的內(nèi)容后面會(huì)逐個(gè)展開(kāi)。1.3 技術(shù)選型上的取舍在技術(shù)選型時(shí)我考慮過(guò)三條路Clutter鴻蒙原生、Unity導(dǎo)出鴻蒙包、Flutter Flame。Clutter原生開(kāi)發(fā)的性能上限最高但開(kāi)發(fā)周期長(zhǎng)我要用不同平臺(tái)維護(hù)兩套代碼Unity做2D游戲很成熟但對(duì)于一個(gè)輕量休閑游戲來(lái)說(shuō)工程重、包體大最后選了Flutter Flame理由有三點(diǎn)團(tuán)隊(duì)已經(jīng)有Dart/Flutter經(jīng)驗(yàn)上手成本低。Flame提供了游戲循環(huán)、組件管理、碰撞檢測(cè)等基礎(chǔ)設(shè)施不需要從零造輪子。鴻蒙特化社區(qū)維護(hù)了flutter_flutter分支已經(jīng)有真機(jī)運(yùn)行的案例不是“畫(huà)餅”狀態(tài)。這里也提醒一下如果你對(duì)Flutter還不熟建議先把Flutter基礎(chǔ)跑通再來(lái)看這個(gè)項(xiàng)目。游戲開(kāi)發(fā)和普通業(yè)務(wù)開(kāi)發(fā)不一樣狀態(tài)管理、渲染樹(shù)、生命周期這些概念會(huì)以更復(fù)雜的方式交織在一起沒(méi)有基礎(chǔ)直接上會(huì)有點(diǎn)痛苦。2. 開(kāi)發(fā)環(huán)境搭建與鴻蒙適配要點(diǎn)2.1 Flutter SDK 與鴻蒙側(cè)SDK的配置細(xì)節(jié)鴻蒙Flutter開(kāi)發(fā)環(huán)境搭建可能是整個(gè)項(xiàng)目里最容易勸退新人的地方。首先你需要的不是Flutter官網(wǎng)的SDK而是OpenHarmony社區(qū)的flutter_flutter分支。我使用的是3.7.24版本這個(gè)版本在社區(qū)里經(jīng)過(guò)較多驗(yàn)證配套的DevEco Studio版本也比較好確認(rèn)。具體步驟如下克隆flutter_flutter倉(cāng)庫(kù)git clone https://gitee.com/openharmony-sig/flutter_flutter.git。切換分支到flutter_3.7.24之類(lèi)的release分支。把這個(gè)flutter命令加到PATH環(huán)境變量里注意要覆蓋掉之前安裝的Flutter官方SDK。安裝鴻蒙側(cè)的DevEco Studio建議5.0及以上版本并在DevEco里配置好鴻蒙SDK路徑。運(yùn)行flutter doctor確認(rèn)能看到HarmonyOS相關(guān)的檢查項(xiàng)。我用的是macOS環(huán)境Windows下流程類(lèi)似但有些命令行工具路徑會(huì)有差異。環(huán)境變量配置好以后別忘了source一下或者重開(kāi)終端否則容易遇到“flutter命令還是舊版本”的詭異問(wèn)題。提示flutter_flutter分支更新速度比官方慢所以版本號(hào)不要追新。穩(wěn)定大于一切特別是做游戲這種對(duì)運(yùn)行穩(wěn)定性要求高的項(xiàng)目。2.2 創(chuàng)建鴻蒙Flutter工程的正確姿勢(shì)環(huán)境配好以后創(chuàng)建工程不要直接用flutter create .因?yàn)槟J(rèn)模板不一定帶鴻蒙的工程結(jié)構(gòu)。正確做法是先創(chuàng)建一個(gè)普通Flutter工程flutter create watermelon_game。然后在工程根目錄下手動(dòng)添加鴻蒙的ohos目錄或者直接從社區(qū)模板里拷貝一份鴻蒙工程骨架。在pubspec.yaml里配置好依賴(lài)比如flame和forge2d。用DevEco Studio打開(kāi)ohos目錄配置好簽名信息。先跑一個(gè)空工程做驗(yàn)證確認(rèn)鴻蒙真機(jī)上能出Flutter的默認(rèn)計(jì)數(shù)頁(yè)面再開(kāi)始寫(xiě)游戲邏輯。這個(gè)“先跑通空工程”的步驟特別重要因?yàn)轼櫭蒄lutter環(huán)境的問(wèn)題往往不是代碼問(wèn)題而是工程配置問(wèn)題。如果空工程能跑后面出問(wèn)題就可以聚焦在游戲邏輯上如果空工程都跑不起來(lái)先解決環(huán)境問(wèn)題再繼續(xù)否則會(huì)浪費(fèi)很多時(shí)間在錯(cuò)誤方向上看日志。2.3 鴻蒙端運(yùn)行Flutter的常見(jiàn)坑位我第一次往鴻蒙真機(jī)部署Flutter應(yīng)用時(shí)遇到過(guò)幾個(gè)記憶深刻的問(wèn)題熱重載失效鴻蒙端的熱重載支持比Android端弱有時(shí)候改了代碼點(diǎn)熱重載沒(méi)反應(yīng)需要手動(dòng)重新運(yùn)行。這跟熱詞的“flutter熱重載后瀏覽器沒(méi)更新”是類(lèi)似場(chǎng)景建議在鴻蒙上開(kāi)發(fā)時(shí)把熱重載當(dāng)作“輔助手段”不要依賴(lài)核心邏輯改動(dòng)直接全量重啟。依賴(lài)下載失敗Flutter默認(rèn)從Google的存儲(chǔ)下載依賴(lài)在國(guó)內(nèi)網(wǎng)絡(luò)環(huán)境下經(jīng)常失敗。解決辦法是在環(huán)境變量里配置鏡像源比如FLUTTER_STORAGE_BASE_URL指向國(guó)內(nèi)鏡像這個(gè)在鴻蒙場(chǎng)景下同樣適用。CMake報(bào)錯(cuò)如果鴻蒙工程里涉及native插件CMake配置出問(wèn)題會(huì)報(bào)一堆錯(cuò)。這個(gè)不常遇到但如果你的Flutter插件里有native代碼就要提前確認(rèn)鴻蒙側(cè)是否支持該插件的實(shí)現(xiàn)。還有一個(gè)細(xì)節(jié)鴻蒙上flutter showLicensePage之類(lèi)的內(nèi)置頁(yè)面主題顏色可能跟Android/iOS不一樣這屬于平臺(tái)差異不影響游戲主流程但如果你做設(shè)置頁(yè)這類(lèi)入口要注意適配。3. 游戲核心邏輯從物理世界到合成規(guī)則3.1 物理引擎選型Flame還是Forge2D合成大西瓜的物理碰撞是整個(gè)游戲的地基我直接用了Flame游戲引擎配合Forge2D物理引擎。Forge2D是Box2D的Dart移植版而Flame里的flame_forge2d包把兩者無(wú)縫集成在一起讓我能以組件的方式管理物理體和渲染體。為什么不用純Flame的自定義碰撞因?yàn)樾蓍e游戲的物理模擬對(duì)實(shí)時(shí)性、穩(wěn)定性的要求很高自己寫(xiě)碰撞檢測(cè)很容易出現(xiàn)“水果穿?!薄皬椞惓!边@些問(wèn)題。Forge2D是成熟的物理引擎重力、碰撞、摩擦、彈性系數(shù)都有成熟算法我只需要調(diào)參數(shù)就行。用生活類(lèi)比來(lái)解釋Flame是游戲的“身體”負(fù)責(zé)游戲循環(huán)、動(dòng)畫(huà)、音頻管理Forge2D是“物理規(guī)則”負(fù)責(zé)重力、碰撞、彈跳這些物理行為。兩者配合我只需要定義水果的物理屬性半徑、密度、摩擦系數(shù)、彈性剩下的交給引擎計(jì)算。3.2 碰撞檢測(cè)與合成判定水果合成判定是游戲的核心邏輯。我的實(shí)現(xiàn)思路是所有水果都是Forge2D里的BodyComponent在初始化時(shí)給每個(gè)水果綁定一個(gè)ContactCallback當(dāng)兩個(gè)水果發(fā)生碰撞時(shí)引擎會(huì)回調(diào)beginContact方法。以下是關(guān)鍵代碼結(jié)構(gòu)的簡(jiǎn)化版本class Fruit extends BodyComponentFruit { final int level; // 水果等級(jí) override Body createBody() { final shape CircleShape()..radius fruitRadius[level]!; final fixtureDef FixtureDef(shape) ..density 1.0 ..friction 0.5 ..restitution 0.2; // ... 創(chuàng)建body定義添加用戶(hù)數(shù)據(jù) } } class FruitCollision extends ContactCallbackFruit, Fruit { override void beginContact(Fruit a, Fruit b) { if (a.level b.level) { // 觸發(fā)合成移除a和b生成level1的水果 } } }這里有一個(gè)關(guān)鍵細(xì)節(jié)Fruit和Fruit的ContactCallback會(huì)攔截所有水果之間的碰撞因此我在回調(diào)里判斷a.level b.level完全相同才觸發(fā)合成。這個(gè)判斷必須放在beginContact里不能在endContact里做否則錯(cuò)過(guò)碰撞瞬間的時(shí)機(jī)兩個(gè)水果會(huì)交錯(cuò)而過(guò)。合成時(shí)還有一個(gè)“位置繼承”的細(xì)節(jié)新生成的高一級(jí)水果位置應(yīng)該在兩個(gè)舊水果碰撞點(diǎn)的中間而不是固定出現(xiàn)在場(chǎng)景某處。我是這么處理的取a.body.position和b.body.position的平均值然后在該位置創(chuàng)建新水果。這樣視覺(jué)上更自然因?yàn)橥婕铱吹降氖恰皟蓚€(gè)葡萄碰在一起變成了一個(gè)橘子”而不是“橘子憑空從天上掉下來(lái)”。3.3 得分判定、Go判定與數(shù)據(jù)管理除了合成邏輯游戲還有得分和結(jié)束判定。得分相對(duì)簡(jiǎn)單每次合成時(shí)根據(jù)水果等級(jí)加分等級(jí)越高加分越多。結(jié)束判定稍微復(fù)雜我設(shè)置了一個(gè)警戒線(xiàn)Y坐標(biāo)當(dāng)任何水果的body.position.y小于警戒線(xiàn)且持續(xù)一段時(shí)間后游戲結(jié)束。這里有一個(gè)容易踩的坑直接以水果坐標(biāo)判斷結(jié)束會(huì)導(dǎo)致“剛碰到警戒線(xiàn)就結(jié)束”的誤判因?yàn)樗赡茉趶椞^(guò)程中臨時(shí)碰到警戒線(xiàn)又彈回去。我加了一個(gè)緩沖機(jī)制只有水果在警戒線(xiàn)以上持續(xù)2秒以上或者有多個(gè)水果同時(shí)越過(guò)警戒線(xiàn)才觸發(fā)結(jié)束。數(shù)據(jù)管理方面我用了一個(gè)簡(jiǎn)單的GameState類(lèi)存儲(chǔ)當(dāng)前分?jǐn)?shù)、最高分、當(dāng)前水果等級(jí)隊(duì)列用ChangeNotifier做狀態(tài)通知UI。這樣做的好處是游戲邏輯和界面解耦后續(xù)接排行榜或者分享功能時(shí)不需要重構(gòu)核心邏輯。class GameState extends ChangeNotifier { int score 0; int highScore 0; bool isGameOver false; void addScore(int points) { score points; if (score highScore) { highScore score; } notifyListeners(); } }4. 視覺(jué)與交互讓游戲“有手感”4.1 水果尺寸、貼圖與縮放適配合成大西瓜里不同等級(jí)的水果尺寸差異很大。最小的葡萄和最大的西瓜半徑差距可能有六七倍。如果直接按像素尺寸硬編碼在不同屏幕寬度的鴻蒙設(shè)備上會(huì)出現(xiàn)比例失衡。我的做法是定義一套相對(duì)半徑表以游戲容器寬度為基準(zhǔn)做等比縮放。貼圖資源用透明底PNG保證視覺(jué)大小和物理半徑盡量匹配。給不同等級(jí)的水果設(shè)置不同的縮放動(dòng)畫(huà)合成時(shí)新水果會(huì)有一個(gè)“長(zhǎng)大”的過(guò)渡效果增強(qiáng)視覺(jué)反饋。這里要注意一個(gè)細(xì)節(jié)物理半徑和貼圖半徑不完全是一回事。物理半徑?jīng)Q定了碰撞箱大小貼圖半徑?jīng)Q定了玩家看到的視覺(jué)大小。如果物理半徑比貼圖小很多玩家會(huì)看到“水果還沒(méi)碰到就合成了”如果物理半徑比貼圖大很多玩家會(huì)看到“水果重疊了好大一塊還沒(méi)觸發(fā)合成”。這兩者需要反復(fù)測(cè)試調(diào)整我在項(xiàng)目里把物理半徑設(shè)為貼圖視覺(jué)半徑的0.85倍左右碰撞手感比較自然。4.2 觸摸拖拽、瞄準(zhǔn)線(xiàn)與拋落手感合成大西瓜的操作方式是“選擇釋放點(diǎn)水果落下”。我在實(shí)現(xiàn)的時(shí)候加了一條瞄準(zhǔn)線(xiàn)提示玩家當(dāng)前手機(jī)觸點(diǎn)對(duì)應(yīng)的釋放位置。瞄準(zhǔn)線(xiàn)的實(shí)現(xiàn)方式很簡(jiǎn)單用一個(gè)PositionComponent畫(huà)一條虛線(xiàn)從屏幕頂部中央延伸到釋放點(diǎn)顏色半透明松手后消失。這里有個(gè)提升手感的小技巧水果釋放后在初始下落階段不要給它附加額外水平速度這樣玩家看到的路徑就是“直直落下”跟瞄準(zhǔn)線(xiàn)一致。如果一開(kāi)始就疊加隨機(jī)水平速度玩家會(huì)感覺(jué)“明明瞄準(zhǔn)了落點(diǎn)卻有偏差”這是操作手感的大忌。還有一點(diǎn)釋放點(diǎn)在容器范圍內(nèi)才有效如果玩家在容器外松手要彈一個(gè)提示或者不響應(yīng)。不然水果會(huì)掉落在物理世界邊界之外直接導(dǎo)致物理模擬異常。4.3 音效與動(dòng)效在鴻蒙上的處理音效主要用flame_audio插件來(lái)播放支持常見(jiàn)的wav/ogg格式。合成、掉落、結(jié)束分別有不同的音效增加游戲反饋感。鴻蒙適配時(shí)需要注意flame_audio底層依賴(lài)的是Flutter的音頻播放能力鴻蒙分支對(duì)音頻的支持我認(rèn)為已經(jīng)比較完善目前測(cè)試下來(lái)沒(méi)有遇到格式兼容問(wèn)題但我建議用wav格式兼容性比mp3更好。動(dòng)效方面合成時(shí)的“水果變大”動(dòng)畫(huà)我用了一個(gè)簡(jiǎn)單的TweenAnimationBuilder或者Flame里的ScaleEffect。用Flame的Effect系統(tǒng)更輕量可以直接在組件上掛縮放效果不用額外管理狀態(tài)。掉落時(shí)的“輕微陰影”我用了一個(gè)與水果形狀一致的半透明圓形組件跟隨水果位置移動(dòng)成本低且效果不錯(cuò)。5. 跨平臺(tái)打包與鴻蒙實(shí)機(jī)運(yùn)行5.1 打包鴻蒙應(yīng)用HAP的正確流程Flutter工程打包鴻蒙應(yīng)用不是直接flutter build apk而是要生成鴻蒙側(cè)的HAP包。流程大致是在工程ohos目錄下用DevEco Studio打開(kāi)工程。配置簽名在File Project Structure Signing Configs里勾選自動(dòng)簽名登錄華為賬號(hào)自動(dòng)生成簽名文件。配置module.json5設(shè)置應(yīng)用包名、版本號(hào)、圖標(biāo)等。用DevEco的Build菜單生成HAP包或者用命令行hvigorw assembleHap。這里有一個(gè)容易忽略的問(wèn)題鴻蒙工程里的oh-package.json5需要顯式聲明對(duì)Flutter引擎的依賴(lài)如果沒(méi)有這個(gè)依賴(lài)聲明HAP包安裝到真機(jī)上會(huì)報(bào)“找不到flutter引擎”的錯(cuò)誤。我在第一次打包時(shí)就在這個(gè)坑里爬了將近兩個(gè)小時(shí)。5.2 真機(jī)調(diào)試與性能參數(shù)真機(jī)調(diào)試時(shí)我用的方式是先用USB連接鴻蒙設(shè)備和電腦在DevEco里點(diǎn)擊運(yùn)行把HAP裝到設(shè)備上。這樣能直接看Flutter的日志輸出方便定位問(wèn)題。游戲性能方面合成大西瓜場(chǎng)景通常同時(shí)存在的物體數(shù)量大約是10到30個(gè)Forge2D物理模擬的壓力并不大。在鴻蒙真機(jī)上只要注意兩點(diǎn)基本能流暢運(yùn)行避免頻繁創(chuàng)建和銷(xiāo)毀對(duì)象我用了對(duì)象池來(lái)管理水果組件同一等級(jí)的水果銷(xiāo)毀后不立即釋放而是復(fù)用給下次生成。這樣能明顯減少卡頓。限制物理迭代次數(shù)Forge2D支持設(shè)置velocityIterations和positionIterations數(shù)值越高越精確但越耗性能。我調(diào)成8和3實(shí)測(cè)在真機(jī)上表現(xiàn)良好肉眼看不出物理異常。5.3 常見(jiàn)問(wèn)題速查表下面這個(gè)表格是開(kāi)發(fā)過(guò)程中反復(fù)遇到的幾個(gè)典型問(wèn)題我整理成速查表方便大家對(duì)照排查。問(wèn)題現(xiàn)象可能原因解決方案鴻蒙真機(jī)裝不上HAP簽名未配置或包名沖突在DevEco里配置自動(dòng)簽名檢查包名是否與已安裝應(yīng)用沖突Flutter頁(yè)面白屏無(wú)報(bào)錯(cuò)flutter引擎so未打包進(jìn)HAP檢查oh-package.json5是否聲明Flutter依賴(lài)熱重載不生效鴻蒙分支對(duì)熱重載支持不完整手動(dòng)重新運(yùn)行不依賴(lài)熱重載水果穿透或合成失效物理半徑與貼圖半徑不匹配調(diào)整CircleShape.radius確保物理體覆蓋視覺(jué)體音頻播放無(wú)聲音音源格式不支持換成wav格式檢查資源路徑應(yīng)用啟動(dòng)慢首次加載引擎耗時(shí)較長(zhǎng)盡量使用release包測(cè)試啟動(dòng)速度debug包本身會(huì)更慢6. 鴻蒙Flutter開(kāi)發(fā)的邊界與心得在鴻蒙上用Flutter做游戲聽(tīng)起來(lái)是件有點(diǎn)“折騰”的事但實(shí)際體驗(yàn)下來(lái)可行性和穩(wěn)定性比我想象中要好。這個(gè)合成大西瓜項(xiàng)目從開(kāi)始搭建環(huán)境到最終跑通真機(jī)我大概用了兩個(gè)周末其中一半時(shí)間花在環(huán)境配置和排錯(cuò)上真正寫(xiě)游戲邏輯反而比較順利。我個(gè)人在實(shí)際操作中的體會(huì)是Flutter跨平臺(tái)開(kāi)發(fā)的核心價(jià)值不在“某一天一套代碼跑所有平臺(tái)”而在于“業(yè)務(wù)邏輯和UI層的高度復(fù)用”。在鴻蒙生態(tài)還不算完全成熟的前期用Flutter把游戲邏輯先跑起來(lái)后續(xù)再做鴻蒙原生適配成本也可以控制得比較低。最后分享一個(gè)小技巧在做鴻蒙Flutter開(kāi)發(fā)時(shí)多利用DevEco Studio自帶的日志過(guò)濾器只保留flutter和DartVM相關(guān)的tag日志噪音會(huì)小很多。開(kāi)發(fā)節(jié)奏和心態(tài)也很重要——鴻蒙的Flutter支持還在快速迭代中保持跟社區(qū)更新跑一版就“鎖”一版依賴(lài)不要頻繁升級(jí)SDK版本。希望這個(gè)項(xiàng)目經(jīng)驗(yàn)?zāi)軒偷秸谧咄瑯勇肪€(xiàn)的同學(xué)也期待看到更多人把好玩的Flutter應(yīng)用帶到鴻蒙設(shè)備上。