修复规则项多选时的错位问题

This commit is contained in:
HapeLee
2026-05-11 03:28:12 +08:00
parent bd814dea8f
commit 3042131457
2 changed files with 69 additions and 60 deletions
@@ -106,14 +106,6 @@ fun ReplaceRuleScreen(
var selectedTabIndex by remember { mutableIntStateOf(0) }
val tabItems = remember(groups) { listOf("全部") + groups }
val filteredRules = remember(uiState.items, selectedTabIndex, tabItems) {
val targetGroup = tabItems.getOrNull(selectedTabIndex)
if (targetGroup == null || selectedTabIndex == 0) {
uiState.items
} else {
uiState.items.filter { it.group == targetGroup }
}
}
val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
viewModel.moveItemInList(from.index, to.index)
@@ -407,7 +399,7 @@ fun ReplaceRuleScreen(
),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(filteredRules, key = { it.id }) { ui ->
items(rules, key = { it.id }) { ui ->
ReorderableSelectionItem(
state = reorderableState,
key = ui.id,
@@ -16,8 +16,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedback
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalHapticFeedback
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import androidx.compose.ui.util.fastFirstOrNull
@Composable
fun <T, ID> DraggableSelectionHandler(
@@ -30,69 +29,87 @@ fun <T, ID> DraggableSelectionHandler(
haptic: HapticFeedback = LocalHapticFeedback.current,
) {
val latestSelectedIds by rememberUpdatedState(selectedIds)
val latestOnSelectionChange by rememberUpdatedState(onSelectionChange)
val latestIdProvider by rememberUpdatedState(idProvider)
val latestItems by rememberUpdatedState(items)
var isAddingMode by remember { mutableStateOf(true) }
var lastProcessedIndex by remember { mutableIntStateOf(-1) }
fun findItemAtOffset(offsetY: Float): Pair<Int, T>? {
val itemInfo = listState.layoutInfo.visibleItemsInfo
.firstOrNull { item ->
offsetY >= item.offset && offsetY <= item.offset + item.size
}
fun findItemAtOffset(offsetY: Float): Pair<Int, ID>? {
val layoutInfo = listState.layoutInfo
val itemsInfo = layoutInfo.visibleItemsInfo
if (itemsInfo.isEmpty()) return null
return itemInfo?.let { info ->
items.getOrNull(info.index)?.let { item ->
info.index to item
}
// 核心修复:校准坐标系。
// offsetY 是相对于此 Box 顶部的。
// item.offset 是相对于视口起始位置(通常包含 contentPadding)的。
// 当开启模糊时,LazyColumn 占据全屏但有 topContentPadding。
// item.offset 在 Compose 的 LazyListLayoutInfo 中是相对于“内容区域”开始计算的。
val adjustedY = offsetY - layoutInfo.beforeContentPadding
val itemInfo = itemsInfo.fastFirstOrNull { item ->
adjustedY >= item.offset && adjustedY <= (item.offset + item.size)
} ?: return null
@Suppress("UNCHECKED_CAST")
val id = try {
itemInfo.key as ID
} catch (e: Exception) {
latestItems.getOrNull(itemInfo.index)?.let { latestIdProvider(it) } ?: return null
}
return itemInfo.index to id
}
fun applySelection(id: ID, add: Boolean) {
val current = latestSelectedIds
onSelectionChange(
if (add) current + id else current - id
)
if (add) {
if (!current.contains(id)) {
latestOnSelectionChange(current + id)
}
} else {
if (current.contains(id)) {
latestOnSelectionChange(current - id)
}
}
}
Box(
modifier = modifier.pointerInput(Unit) {
coroutineScope {
launch {
detectTapGestures(
onTap = { offset ->
findItemAtOffset(offset.y)?.let { (_, item) ->
val id = idProvider(item)
applySelection(id, !latestSelectedIds.contains(id))
modifier = modifier
.pointerInput(listState) {
detectTapGestures(
onTap = { offset ->
findItemAtOffset(offset.y)?.let { (_, id) ->
applySelection(id, !latestSelectedIds.contains(id))
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
}
}
)
}
.pointerInput(listState) {
detectDragGestures(
onDragStart = { offset ->
findItemAtOffset(offset.y)?.let { (index, id) ->
lastProcessedIndex = index
isAddingMode = !latestSelectedIds.contains(id)
applySelection(id, isAddingMode)
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}
},
onDrag = { change, _ ->
findItemAtOffset(change.position.y)?.let { (index, id) ->
if (index != lastProcessedIndex) {
lastProcessedIndex = index
applySelection(id, isAddingMode)
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
}
}
)
}
launch {
detectDragGestures(
onDragStart = { offset ->
findItemAtOffset(offset.y)?.let { (index, item) ->
lastProcessedIndex = index
val id = idProvider(item)
isAddingMode = !latestSelectedIds.contains(id)
applySelection(id, isAddingMode)
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}
},
onDrag = { change, _ ->
findItemAtOffset(change.position.y)?.let { (index, item) ->
if (index != lastProcessedIndex) {
lastProcessedIndex = index
applySelection(idProvider(item), isAddingMode)
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
}
}
},
onDragEnd = { lastProcessedIndex = -1 },
onDragCancel = { lastProcessedIndex = -1 }
)
}
if (change.pressed) change.consume()
},
onDragEnd = { lastProcessedIndex = -1 },
onDragCancel = { lastProcessedIndex = -1 }
)
}
}
)
}
}