定ID與布局切換【鴻蒙心跡】)
相冊類應用做成兩列網(wǎng)格并不難真正容易出問題的是用戶正在批量選圖的時候把折疊屏展開。折疊前第三張照片在第二行展開后同一張可能移到第一行如果業(yè)務(wù)把“第幾個格子被選中”作為記錄布局重排以后勾選標記就會落到另一張照片上。更尷尬的是頁面明明還顯示“已選擇 3 張”底部導出拿到的卻不是剛才圈中的那三張。這次把問題放進一個小工程PhotoBatch。它不是大型圖庫也沒有接入相冊媒體庫只用八張帶穩(wěn)定業(yè)務(wù) ID 的示例素材固定選擇P102、P104、P107選擇數(shù)量為3。界面有2 列 / 4 列兩種排版展開態(tài)展示四列編輯狀態(tài)叫EDITING。討論重點是三件事誰保管選中結(jié)果、網(wǎng)格重排怎樣處理、離開編輯模式該不該清空它。圖 01兩列與四列布局切換的概念封面。示例配圖用于解釋穩(wěn)定 ID不代表實機折疊動作已經(jīng)測試。## 一、我不想再讓數(shù)組下標承擔業(yè)務(wù)身份如果收藏列表永遠不插入、不刪除、不排序索引看起來非常方便。selectedIndexes [1, 3, 6]能在當下圈出三張圖??梢坏┣袚Q排序方式甚至只是讓用戶從大屏換到小屏重新加載數(shù)據(jù)下標就只能描述一個位置不能描述一張具體照片。位置由布局決定身份則應該由業(yè)務(wù)數(shù)據(jù)決定把二者綁在一起遲早會有問題。ArkUI 的 Grid/GridItem 提供了網(wǎng)格展示和選擇狀態(tài)回調(diào)華為官方創(chuàng)建網(wǎng)格文檔還特別提示選擇記錄建議使用不會隨位置變化的唯一標識。文檔記載自 API 26.0.0 起 Grid 支持編輯模式下的手指滑動多選包括enableEditMode、editModeOptions、selectable、selected與onSelect。這意味著功能不能無條件宣稱覆蓋所有舊系統(tǒng)。需要照顧更早目標版本的產(chǎn)品可以先采用普通點擊選中再按設(shè)備能力逐步增加滑動多選。https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/arkts-layout-development-create-grid先把示例數(shù)據(jù)和選中集合寫成可測試的純業(yè)務(wù)模型。這段代碼要解決的問題是布局怎么變化都以照片 ID 判斷是否選中并在數(shù)據(jù)刷新時剔除已不存在的 ID。arktsinterface PhotoAsset { id: string title: string}class PhotoSelection { private ids: Setstring new Setstring() setSelected(id: string, value: boolean): void { if (value) this.ids.add(id) else this.ids.delete(id) } has(id: string): boolean { return this.ids.has(id) } snapshot(): string[] { return Array.from(this.ids.values()) } reconcile(assets: PhotoAsset[]): string[] { const alive new Setstring(assets.map((p: PhotoAsset): string p.id)) this.ids.forEach((id: string) { if (!alive.has(id)) this.ids.delete(id) }) return this.snapshot() }}Set 適合存“已經(jīng)選擇了哪些照片”但不意味著可以直接把一個原地修改的 Set 扔進任意狀態(tài)裝飾器就期待 UI 自動重繪。業(yè)務(wù)層保管集合展示層每次收到變化后取出新數(shù)組賦給 State更容易觀察狀態(tài)更新。reconcile() 也不是強行取消所有選擇它僅處理已從當前素材集合中消失的 ID如果圖庫采用分頁模式還要區(qū)分“當前頁沒加載到”和“業(yè)務(wù)記錄已經(jīng)被刪除”不能對分頁列表直接做全量回收。## 二、選中態(tài)留在業(yè)務(wù)里網(wǎng)格只負責表達GridItem 知道自己能否被選中也會在用戶切換選擇時給出一個布爾值。它不必知道“這張照片在整個項目里排第幾”。當 columnsTemplate 從兩列改成四列Grid 重新排布子項當前圖片仍然以 P102 等 ID 去讀取業(yè)務(wù)記錄視覺勾選就能找回正確對象。下面是頁面的關(guān)鍵結(jié)構(gòu)。代碼解決的是把官方的編輯模式回調(diào)與自己的選中賬本連接起來而不是實現(xiàn)一套新的手勢識別器。真實工程需配合完整的資源文件、頁面容器和圖項 Builder這里用文字卡片縮小代碼范圍。arktsEntryComponentstruct PhotoBatchPage { State columns: number 4 State editMode: boolean true State chosen: string[] [‘P102’, ‘P104’, ‘P107’] private assets: PhotoAsset[] [ { id: ‘P101’, title: ‘雪山’ }, { id: ‘P102’, title: ‘花朵’ }, { id: ‘P103’, title: ‘湖泊’ }, { id: ‘P104’, title: ‘森林’ }, { id: ‘P105’, title: ‘海岸’ }, { id: ‘P106’, title: ‘城市’ }, { id: ‘P107’, title: ‘草地’ }, { id: ‘P108’, title: ‘建筑’ } ] private select(id: string, value: boolean): void { this.chosen value ? […new Set([…this.chosen, id])] : this.chosen.filter((key: string): boolean key ! id) } build() { Column() { Text(已選擇 ${this.chosen.length} 張) Grid() { ForEach(this.assets, (item: PhotoAsset) { GridItem() { Text(${item.id} ${item.title}) } .selectable(true) .selected(this.chosen.includes(item.id)) .onSelect((selected: boolean) this.select(item.id, selected)) }, (item: PhotoAsset): string item.id) } .columnsTemplate(this.columns 4 ? ‘1fr 1fr 1fr 1fr’ : ‘1fr 1fr’) .enableEditMode(this.editMode) .onEditModeChange((enabled: boolean) { this.editMode enabled }) .editModeOptions({ useDefaultMultiSelectStyle: true, enableTwoFingerMultiSelect: true }) } }}這段代碼假設(shè)項目的 SDK 和運行設(shè)備具備相應 Grid 編輯模式能力不能直接拿到舊版工程中宣稱通用。State chosen 是新的數(shù)組減少框架感知不到內(nèi)部集合變化的風險。ForEach 使用唯一 ID 作 key而不是索引否則同一業(yè)務(wù)記錄被移動到其他位置時組件復用和勾選樣式可能互相影響。示例中第一次打開就預置了三個選中值目的是讓圖片、日志與狀態(tài)一致不是在暗示系統(tǒng)默認會替用戶選圖。*圖 02獨立制作的 DevEco Studio 風格示意圖右側(cè)模擬器顯示四列布局與 P102/P104/P107 三項選擇底部 HiLog 為預設(shè)的教學日志不是真實設(shè)備采集證據(jù)。*## 三、布局改變是一件事編輯會話結(jié)束是另一件事從折疊態(tài)的 2 列切到展開態(tài)的 4 列我希望視覺重新計算不希望業(yè)務(wù) ID 集合發(fā)生變化。這個區(qū)分可以用一個極小的方法表達輸入最新有效寬度只改變列數(shù)不碰 chosen。寬度最好來自實際容器的測量值而不是僅根據(jù)設(shè)備型號猜測“展開”。分屏、懸浮窗口和橫豎屏變化同樣可能改變可用區(qū)域。第三段代碼專門用于處理這個邊界。這里的 600vp 是 Demo 人為選擇的閾值不是官方規(guī)定的折疊屏分界線。閾值要由真實頁面最小卡片寬度、留白和字體測量共同決定。arktsprivate adaptColumns(widthVp: number): void { if (!Number.isFinite(widthVp) || widthVp 0) return const next widthVp 600 ? 4 : 2 if (next ! this.columns) { this.columns next console.info([PhotoBatch] columns${next} selected${this.chosen.join(,)}) }}// 掛到實際需要測量的容器上// .onAreaChange((_old: Area, now: Area) {// this.adaptColumns(Number(now.width))// })開發(fā)圖使用展開態(tài)columns4日志寫明selectedP102,P104,P107。把屏幕縮回兩列后應該仍然是這三個 ID唯一變化是卡片在網(wǎng)格中的位置。編輯模式結(jié)束則由產(chǎn)品決定是否清空如果底部操作欄只是暫時收起我傾向于保留選擇待用戶明確點擊“取消選擇”才清除如果進入新相冊則應按相冊會話創(chuàng)建新的集合。不要把onEditModeChange(false)簡單等同于清空因為系統(tǒng)返回和手勢也可能導致編輯模式變化。## 四、先看可驗證的狀態(tài)再談操作手感本文演示態(tài)固定為PhotoBatch任務(wù)編號BATCH_20261016_15狀態(tài)EDITING已選擇 3 張、當前四列。手機界面示意展示三個綠色勾選標識底部操作入口是“導出所選”和“取消選擇”。數(shù)據(jù)只表達選中不代表真的把相冊文件導出到了磁盤。類似批量刪除、批量共享這樣的操作都應該在業(yè)務(wù)層二次確認選中的 ID 是否仍然有效。*圖 03純屏圖庫界面示意。實際項目應繼續(xù)使用屏幕朗讀工具核對復選標記名稱并在真機上觀察雙指進入編輯模式的手勢沖突。*怎樣檢查這個功能我會做一個很樸素的順序先選三張再從兩列改為四列確認選中 ID 沒變插入一張新照片確認原有選擇沒有偏移從列表中真正刪除一項再用reconcile()清理該項最后退出編輯模式并重新進入核對產(chǎn)品定義的保留策略。測試日志應同時打印業(yè)務(wù) ID 和列數(shù)避免只記錄“選中數(shù)量 3”卻無法判斷選中對象是否正確。這里還要注意Grid 的“滑動多選”并非免費的所有場景都適用??ㄆ瑑?nèi)部如果有橫向輪播、拖拽排序、長按菜單多種手勢會競爭觸摸事件。官方文檔也提到了自定義樣式下的手勢裁決。最安全的路徑是先保留系統(tǒng)默認多選樣式在確實需要自定義時再逐項驗證手勢熱區(qū)、輔助功能和低版本兼容行為。## 五、狀態(tài)穩(wěn)定比布局漂亮更重要這套方案的核心不是“從兩列變四列”這種效果而是讓布局變化只影響幾何分配讓業(yè)務(wù) ID 決定每一張照片的身份。它適用于折疊屏圖庫、平板素材管理以及窗口大小可變化的文件選擇器卻不覆蓋媒體訪問權(quán)限、云相冊同步?jīng)_突也不解決跨設(shè)備事務(wù)一致性。實現(xiàn)落地前還需要在目標 SDK 下編譯enableEditMode等接口對低版本設(shè)置明確的交互降級策略。本文配圖為演示素材示例未在真實 DevEco 項目或設(shè)備上運行。把“布局策略”和“選中集合”這兩條線分清才不容易在多形態(tài)適配時把舊問題重新帶回來。