# 策展 · X (Twitter) 🔥

> 作者：Adit Lal (@aditlal) · 平台：X (Twitter) · 日期：2026-03-17

> 原始來源：https://x.com/aditlal/status/2033801000881115571

## 中文摘要

開發者剛開源了「ComposePinchGrid」——一個為 Compose Multiplatform 打造的 Google Photos 風格捏縮網格元件。可在 Android、iOS、Desktop 和 Web 上運作，只需 3 行程式碼，且零 Material 依賴。

**手勢辨識難點** 真正的難點不在網格本身，而在手勢辨識。transformable() 與 LazyVerticalGrid 捲軸的衝突在於兩者搶奪相同的指標事件。解決方案是使用原始的 pointerInput 搭配 PointerEventPass.Initial——在網格捲軸處理器之前截獲 2 指捏縮手勢，單指捲軸則完全通過。

**元件功能清單** 元件提供的功能包括：
- 非對稱閾值調整（捏展自然產生較弱的縮放，透過補償實現平衡）
- 死區過濾（消除手指顫抖造成的抖動）
- 呼吸動畫效果（使用 graphicsLayer 達到零重組）
- 平台原生震動回饋（Android 使用 CLOCK_TICK，iOS 使用 UISelectionFeedbackGenerator）

**跨平台架構** 架構採用 Kotlin Multiplatform 開發，支援 Android、iOS、Desktop 和 Wasm，採用 Apache 2.0 授權。

**原生手勢選擇** 技術特點在於為何選擇原始 pointerInput 而非 transformable——transformable 乍看是捏縮手勢的顯然選擇，卻與 LazyVerticalGrid 捲軸衝突。兩者競爭相同的指標事件，單指捲軸會被誤判為變換開始，導致網格凍結而非捲動。ComposePinchGrid 使用 awaitEachGesture 搭配 awaitFirstDown，只在偵測到 2 個或更多指標時才消費事件，單指捲軸完全不受影響。

**閾值參數調整** 使用者可透過 thresholdFraction 參數控制觸發欄數變更需要的捏縮程度（較低數值代表更靈敏），pinchOutThresholdMultiplier 在 0.85f 時讓放大需要 15% 較少的手指移動，使兩個方向感覺同樣反應靈敏。deadZone 預設 0.01f 過濾微小動作，防止手指顫抖造成的抖動。

**呼吸動畫設計** 呼吸動畫在捏縮手勢期間讓網格隨指標縮放，提供即時視覺回饋，使用 graphicsLayer 實現零重組的 GPU 變換。breathingScaleIntensity 預設 0.10f（±10% 縮放），可設為 0f 禁用；breathingReturnDuration 預設 150ms 控制釋放時回歸動畫速度。

**捲軸位置保留** 欄數變更時，網格會快照首個可見項目索引並在變更後復原，防止天真的 GridCells.Fixed() 交換帶來的卡頓捲軸跳躍。最佳實踐是提供穩定的 key 值。

**程式控制支援** 手勢感應可完全禁用，保留程式控制能力，透過 snapToColumn() 方法實現鍵盤、按鈕或無障礙功能的欄數控制。performHapticFeedback 在每次欄數變更時自動觸發，可透過 hapticEnabled = false 禁用。

**效能考量** 效能考量上：
- 呼吸動畫使用 graphicsLayer 純繪製階段零重組
- 欄數變更單次 mutableIntStateOf 更新觸發網格重組
- 震動回饋內聯執行無協程開銷
- 捲軸復原採標準 Compose 模式的 LaunchedEffect + snapshotFlow

**高度可配置性** 元件的可配置性極高——每個參數都有經過調整的預設值，但可覆蓋任何設定。thresholdFraction 預設 0.45f 提供反應靈敏但不易意外觸發的平衡；pinchOutThresholdMultiplier 預設 0.85f 補償捏展自然較弱的特性；breathingScaleIntensity 預設 0.10f 提供適度視覺回饋；transitionSpec 預設即時重排，也支援 crossfade 等過渡效果。

## 標籤

開源專案, Kotlin, Android, iOS, Web, Compose Multiplatform
