戰(zhàn)拆解)
文章目錄前言為什么這個(gè)問題經(jīng)常被寫亂場景資訊列表首屏加載骨架塊設(shè)計(jì)實(shí)操步驟先把頁面目標(biāo)想清楚完整示例資訊卡片骨架組件把關(guān)鍵代碼一段段拆開容易踩坑的點(diǎn)優(yōu)化建議骨架組件要輕但不能失真寫在最后前言骨架屏不是灰色占位塊越多越專業(yè)。它應(yīng)該貼近真實(shí)頁面結(jié)構(gòu)讓用戶知道內(nèi)容正在加載也讓頁面從加載到完成時(shí)不會明顯跳動。這篇單獨(dú)聊通用加載這個(gè)場景。重點(diǎn)不是堆 API而是把骨架屏抽成足夠小的 ArkUI 構(gòu)建器讓加載態(tài)貼近真實(shí)頁面結(jié)構(gòu)。為什么這個(gè)問題經(jīng)常被寫亂頁面骨架如何做成組件 這類內(nèi)容很容易被寫成“代碼能跑就算講完了”但對初學(xué)者來說這恰恰是最不夠的地方。真正讓人卡住的往往不是某個(gè)組件名記不住而是不知道這段代碼為什么要這樣拆、狀態(tài)為什么要這樣放、以后需求變化時(shí)應(yīng)該從哪里改。所以這篇文章不只想給你一個(gè)能跑的例子更想把背后的判斷過程講清楚。你只要把這個(gè)判斷過程吃透后面自己改頁面、補(bǔ)需求、查問題時(shí)心里會穩(wěn)很多。場景資訊列表首屏加載骨架屏不是把頁面涂成一片灰色。它應(yīng)該告訴用戶這里會有標(biāo)題、這里會有摘要、這里會有列表卡片。資訊、課程、訂單、商品列表這類頁面如果加載時(shí)只放一個(gè)轉(zhuǎn)圈用戶不知道還要等多久如果骨架結(jié)構(gòu)貼近真實(shí)內(nèi)容等待感會明顯降低。我做骨架組件時(shí)會控制復(fù)雜度。骨架應(yīng)該是輕量的 UI 片段不應(yīng)該復(fù)制一份完整業(yè)務(wù)卡片。復(fù)制得越像維護(hù)成本越高抽得太粗又失去降低跳動的意義。骨架屏不是裝飾它是“頁面馬上會長什么樣”的預(yù)告。骨架塊設(shè)計(jì)骨架塊對應(yīng)真實(shí)內(nèi)容尺寸建議注意點(diǎn)SkeletonLine標(biāo)題、摘要12 到 22 vp 高寬度不要全滿SkeletonAvatar頭像、圖標(biāo)32 到 48 vp圓角貼近真實(shí)圖形SkeletonCard列表卡片接近真實(shí)卡片高度避免加載完成大跳動SkeletonAction按鈕32 到 40 vp 高不要太搶眼實(shí)操步驟先畫真實(shí)頁面結(jié)構(gòu)再反推骨架塊而不是先畫灰塊。抽出最小單元SkeletonLine復(fù)用顏色和圓角。再組合出ArticleSkeletonCard這類業(yè)務(wù)骨架。加載態(tài)和內(nèi)容態(tài)用同一塊頁面區(qū)域切換避免高度差過大。骨架數(shù)量與首屏可見內(nèi)容接近不要一次渲染幾十個(gè)灰塊。接口失敗時(shí)切到錯(cuò)誤態(tài)不要讓骨架無限顯示。先把頁面目標(biāo)想清楚在真正寫代碼之前先別急著盯著 API。更有用的做法是先想清楚這個(gè)頁面到底想解決什么問題用戶最在意的反饋是什么哪些狀態(tài)必須一直保持一致。當(dāng)你先把這條主線想明白再回頭看組件和狀態(tài)設(shè)計(jì)很多選擇都會順理成章。對小白來說這一步尤其重要因?yàn)樗軒湍銖摹罢罩甭^渡到“看得懂、改得動”。完整示例資訊卡片骨架組件interfaceArticleItem{id:numbertitle:stringsummary:stringauthor:string}EntryComponentstruct SkeletonComponentPage{Stateloading:booleantrueStatefailed:booleanfalseprivatearticles:ArticleItem[][{id:1,title:ArkUI 列表加載的幾個(gè)細(xì)節(jié),summary:從骨架屏、空態(tài)到錯(cuò)誤兜底整理一個(gè)更完整的加載體驗(yàn)。,author:編輯部},{id:2,title:HarmonyOS7 頁面狀態(tài)管理實(shí)踐,summary:用明確狀態(tài)減少頁面閃動和重復(fù)判斷。,author:技術(shù)團(tuán)隊(duì)}]BuilderSkeletonLine(widthValue:Length,heightValue:number,radiusValue:number){Blank().width(widthValue).height(heightValue).backgroundColor(#E6EAF0).borderRadius(radiusValue)}BuilderArticleSkeletonCard(){Row({space:12}){this.SkeletonLine(48,48,8)Column({space:10}){this.SkeletonLine(64%,18,4)this.SkeletonLine(92%,14,4)this.SkeletonLine(48%,14,4)}.alignItems(HorizontalAlign.Start).layoutWeight(1)}.padding(14).backgroundColor(#FFFFFF).borderRadius(8)}BuilderArticleCard(item:ArticleItem){Row({space:12}){Text(item.author.substring(0,1)).fontSize(18).fontWeight(FontWeight.Bold).fontColor(#FFFFFF).width(48).height(48).textAlign(TextAlign.Center).backgroundColor(#0A59F7).borderRadius(8)Column({space:6}){Text(item.title).fontSize(16).fontWeight(FontWeight.Medium).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})Text(item.summary).fontSize(13).fontColor(#666666).maxLines(2).textOverflow({overflow:TextOverflow.Ellipsis})Text(item.author).fontSize(12).fontColor(#999999)}.alignItems(HorizontalAlign.Start).layoutWeight(1)}.padding(14).backgroundColor(#FFFFFF).borderRadius(8)}privatereload(){this.loadingtruethis.failedfalse}build(){Column({space:12}){Row(){Text(加載示例).fontSize(22).fontWeight(FontWeight.Bold)Blank()Button(this.loading?顯示內(nèi)容:顯示骨架).onClick(()this.loading!this.loading)}.width(100%)if(this.failed){Column({space:8}){Text(加載失敗).fontSize(18).fontWeight(FontWeight.Medium)Text(網(wǎng)絡(luò)不穩(wěn)定請稍后重試。).fontSize(13).fontColor(#666666)Button(重試).onClick(()this.reload())}.alignItems(HorizontalAlign.Start).padding(14).backgroundColor(#FFFFFF).borderRadius(8)}elseif(this.loading){this.ArticleSkeletonCard()this.ArticleSkeletonCard()this.ArticleSkeletonCard()}else{ForEach(this.articles,(item:ArticleItem){this.ArticleCard(item)},(item:ArticleItem)item.id.toString())}}.padding(16).height(100%).backgroundColor(#F5F7FA)}}把關(guān)鍵代碼一段段拆開SkeletonLine()是最小單元只關(guān)心寬、高、圓角。這樣標(biāo)題線、摘要線、頭像占位都能復(fù)用同一套灰色樣式不需要到處復(fù)制backgroundColor。ArticleSkeletonCard()的結(jié)構(gòu)和真實(shí)ArticleCard()接近左側(cè)頭像右側(cè)三行文字。加載完成后高度變化很小頁面不會突然跳動。failed和loading分開處理。骨架只能表示“正在加載”不能拿來表示“加載失敗”。接口失敗后如果骨架一直轉(zhuǎn)用戶會以為還在等。容易踩坑的點(diǎn)骨架和真實(shí)內(nèi)容高度差太大加載完成時(shí)頁面明顯跳動。骨架數(shù)量過多反而增加首屏渲染壓力。把骨架寫成完整業(yè)務(wù)組件的復(fù)制版后續(xù)維護(hù)兩套結(jié)構(gòu)。失敗后仍顯示骨架沒有錯(cuò)誤兜底。所有灰塊寬度都一樣看起來不像真實(shí)內(nèi)容。優(yōu)化建議骨架屏適合超過短暫等待的頁面。如果接口通常幾十毫秒返回強(qiáng)行閃一下骨架反而打擾用戶??梢栽O(shè)置一個(gè)很小的延遲策略請求很快完成就不展示骨架請求超過一定時(shí)間再展示。顏色上不要用太重的灰尤其是在淺色背景里。骨架應(yīng)該低調(diào)地暗示結(jié)構(gòu)不應(yīng)該搶走真實(shí)內(nèi)容的位置感。深色模式下也要單獨(dú)調(diào)整骨架色否則灰塊可能過亮。骨架組件要輕但不能失真骨架屏的價(jià)值在于降低等待感和布局跳動不是復(fù)制一套完整業(yè)務(wù) UI?;覊K要貼近真實(shí)內(nèi)容結(jié)構(gòu)讓用戶知道頁面馬上會出現(xiàn)什么但它又不能和真實(shí)卡片完全同步維護(hù)否則后面改一次業(yè)務(wù)卡片就要改兩份代碼。示例先抽SkeletonLine()作為最小單元再組合成ArticleSkeletonCard()。這樣顏色、圓角和尺寸可以復(fù)用資訊卡片的左圖右文結(jié)構(gòu)也能保留下來加載完成時(shí)頁面不會明顯跳動。寫在最后失敗態(tài)要和加載態(tài)分開。骨架只表示“正在加載”接口失敗后應(yīng)該切到錯(cuò)誤提示和重試入口。如果骨架無限顯示用戶會以為還在等待實(shí)際已經(jīng)沒有下一步了。