動(dòng)畫(huà)治理:?jiǎn)螜谇须p欄時(shí)為什么要先凍結(jié)滾動(dòng)位置)
HarmonyOS 7.0 / API 26 折疊屏斷點(diǎn)動(dòng)畫(huà)治理單欄切雙欄時(shí)為什么要先凍結(jié)滾動(dòng)位置這篇只講一個(gè)點(diǎn)折疊屏斷點(diǎn)切換時(shí)的滾動(dòng)凍結(jié)和布局恢復(fù)。版本邊界先說(shuō)清楚下面的寫(xiě)法面向 HarmonyOS 7.0 / API 26。老版本工程不要直接照搬先確認(rèn) SDK、DevEco Studio、設(shè)備系統(tǒng)版本和模擬器鏡像是否一致。先說(shuō)它解決什么折疊屏從單欄切到雙欄時(shí)如果列表還在慣性滾動(dòng)布局?jǐn)帱c(diǎn)又馬上重算就會(huì)出現(xiàn)卡片跳位、詳情頁(yè)錯(cuò)項(xiàng)、滾動(dòng)位置回彈。解決思路不是把動(dòng)畫(huà)關(guān)掉而是在斷點(diǎn)切換的一小段窗口里凍結(jié)滾動(dòng)輸入先恢復(fù)錨點(diǎn)再釋放手勢(shì)。如果還按 5.0 或 6.0 的舊習(xí)慣處理通常會(huì)遇到三個(gè)問(wèn)題第一代碼能編譯但設(shè)備上行為和預(yù)期不一致第二頁(yè)面狀態(tài)看起來(lái)正常切換場(chǎng)景后就暴露邊界第三性能或體驗(yàn)問(wèn)題不是馬上炸而是用戶(hù)連續(xù)操作后才出現(xiàn)。容易復(fù)現(xiàn)的兩個(gè)場(chǎng)景場(chǎng)景一單欄列表滑動(dòng)中展開(kāi)屏幕列表不能跳到頂部詳情頁(yè)也不能切錯(cuò)項(xiàng)復(fù)現(xiàn)方式很簡(jiǎn)單先把頁(yè)面打開(kāi)到目標(biāo)狀態(tài)再連續(xù)做兩次切換或刷新。這個(gè)時(shí)候要觀(guān)察的不是按鈕有沒(méi)有響應(yīng)而是狀態(tài)有沒(méi)有丟、動(dòng)畫(huà)有沒(méi)有抖、資源有沒(méi)有重復(fù)申請(qǐng)。場(chǎng)景二雙欄切回單欄時(shí)如果當(dāng)前詳情項(xiàng)不在可見(jiàn)區(qū)需要滾動(dòng)到對(duì)應(yīng)錨點(diǎn)第二個(gè)場(chǎng)景更接近線(xiàn)上問(wèn)題用戶(hù)不是按開(kāi)發(fā)者預(yù)設(shè)路徑走而是會(huì)來(lái)回切頁(yè)面、鎖屏、恢復(fù)、換方向、切到后臺(tái)再回來(lái)。這個(gè)時(shí)候如果只看單次點(diǎn)擊問(wèn)題會(huì)被遮住。最小 DemointerfaceBreakpointSnapshot{selectedId:stringscrollOffset:numbermode:single|dual}classFoldBreakpointCoordinator{privatefrozen:booleanfalsefreeze():void{this.frozentrue}release():void{this.frozenfalse}canScroll():boolean{return!this.frozen}nextMode(width:number):single|dual{returnwidth840?dual:single}restore(snapshot:BreakpointSnapshot,width:number):BreakpointSnapshot{constmodethis.nextMode(width)return{selectedId:snapshot.selectedId,scrollOffset:snapshot.scrollOffset,mode}}}EntryComponentstruct FoldBreakpointDemo{privatecoordinator:FoldBreakpointCoordinatornewFoldBreakpointCoordinator()Stateprivatemode:stringsingleStateprivateselectedId:stringitem-8StateprivatecanScroll:stringyesbuild(){Column({space:12}){Text(折疊屏斷點(diǎn)動(dòng)畫(huà)治理).fontSize(22).fontWeight(FontWeight.Bold)Text(modethis.mode, selectedthis.selectedId)Text(canScrollthis.canScroll)Button(模擬展開(kāi)到雙欄).onClick((){constsnapshot:BreakpointSnapshot{selectedId:this.selectedId,scrollOffset:360,mode:single}this.coordinator.freeze()this.canScrollString(this.coordinator.canScroll())constnextthis.coordinator.restore(snapshot,900)this.modenext.modethis.selectedIdnext.selectedIdthis.coordinator.release()this.canScrollString(this.coordinator.canScroll())})}.padding(20).width(100%)}}這個(gè) Demo 的重點(diǎn)不是炫技而是把問(wèn)題壓到最小一個(gè)入口、一個(gè)狀態(tài)變化、一個(gè)驗(yàn)證點(diǎn)。先把這個(gè)跑通再往復(fù)雜頁(yè)面里搬排查成本會(huì)低很多。我會(huì)怎么選方案方案適合場(chǎng)景風(fēng)險(xiǎn)繼續(xù)沿用舊寫(xiě)法舊頁(yè)面、小范圍兼容遇到 7.0 新能力邊界時(shí)不好排查在頁(yè)面內(nèi)臨時(shí)處理快速驗(yàn)證問(wèn)題代碼容易散后面不好復(fù)用抽成獨(dú)立工具或組件多頁(yè)面、多設(shè)備、多狀態(tài)復(fù)用前期要把輸入輸出設(shè)計(jì)清楚我的選擇是第三種。只要這個(gè)能力會(huì)被多個(gè)頁(yè)面用到就不要把判斷邏輯塞在頁(yè)面里。頁(yè)面只負(fù)責(zé)展示能力邊界、異常兜底、版本判斷放到獨(dú)立函數(shù)或組件里。這樣后面改 SDK、換設(shè)備、補(bǔ)兼容邏輯影響面會(huì)小很多。驗(yàn)證清單DevEco Studio 使用支持 HarmonyOS 7.0 / API 26 的版本。真機(jī)或模擬器系統(tǒng)版本和文章里的 API 版本一致。至少跑通上面兩個(gè)場(chǎng)景不只看首屏。如果涉及多設(shè)備、窗口、后臺(tái)恢復(fù)要補(bǔ)一次切換測(cè)試。如果要發(fā)到線(xiàn)上日志里要能看出失敗原因而不是只看到一個(gè)空狀態(tài)。最后總結(jié)這篇把折疊屏斷點(diǎn)切換拆成凍結(jié)、恢復(fù)錨點(diǎn)、釋放輸入三步。這樣處理后單欄和雙欄之間切換不會(huì)因?yàn)闈L動(dòng)慣性把頁(yè)面帶偏。這類(lèi)特性真正有價(jià)值的地方不是知道一個(gè)新名字而是知道它在什么場(chǎng)景該用、什么時(shí)候不該用、怎么復(fù)現(xiàn)問(wèn)題、怎么把修復(fù)沉淀成可復(fù)用代碼。后面再接復(fù)雜頁(yè)面時(shí)先把這個(gè)小 Demo 跑通基本能避開(kāi)一半低級(jí)返工。驗(yàn)證矩陣場(chǎng)景輸入寬度滾動(dòng)狀態(tài)期望單欄轉(zhuǎn)雙欄900慣性滾動(dòng)中先凍結(jié)再恢復(fù)選中項(xiàng)雙欄轉(zhuǎn)單欄600詳情打開(kāi)回到列表錨點(diǎn)快速反復(fù)折疊600/900 循環(huán)連續(xù)切換不重復(fù)觸發(fā)恢復(fù)任務(wù)真正要防的是兩個(gè)異步動(dòng)作搶順序滾動(dòng)還沒(méi)停布局已經(jīng)重算。把凍結(jié)窗口做明確后面性能問(wèn)題也更容易查。