修复规则项多选时的错位问题
This commit is contained in:
@@ -106,14 +106,6 @@ fun ReplaceRuleScreen(
|
|||||||
|
|
||||||
var selectedTabIndex by remember { mutableIntStateOf(0) }
|
var selectedTabIndex by remember { mutableIntStateOf(0) }
|
||||||
val tabItems = remember(groups) { listOf("全部") + groups }
|
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 ->
|
val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
|
||||||
viewModel.moveItemInList(from.index, to.index)
|
viewModel.moveItemInList(from.index, to.index)
|
||||||
@@ -407,7 +399,7 @@ fun ReplaceRuleScreen(
|
|||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
items(filteredRules, key = { it.id }) { ui ->
|
items(rules, key = { it.id }) { ui ->
|
||||||
ReorderableSelectionItem(
|
ReorderableSelectionItem(
|
||||||
state = reorderableState,
|
state = reorderableState,
|
||||||
key = ui.id,
|
key = ui.id,
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedback
|
|||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import kotlinx.coroutines.coroutineScope
|
import androidx.compose.ui.util.fastFirstOrNull
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T, ID> DraggableSelectionHandler(
|
fun <T, ID> DraggableSelectionHandler(
|
||||||
@@ -30,69 +29,87 @@ fun <T, ID> DraggableSelectionHandler(
|
|||||||
haptic: HapticFeedback = LocalHapticFeedback.current,
|
haptic: HapticFeedback = LocalHapticFeedback.current,
|
||||||
) {
|
) {
|
||||||
val latestSelectedIds by rememberUpdatedState(selectedIds)
|
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 isAddingMode by remember { mutableStateOf(true) }
|
||||||
var lastProcessedIndex by remember { mutableIntStateOf(-1) }
|
var lastProcessedIndex by remember { mutableIntStateOf(-1) }
|
||||||
|
|
||||||
fun findItemAtOffset(offsetY: Float): Pair<Int, T>? {
|
fun findItemAtOffset(offsetY: Float): Pair<Int, ID>? {
|
||||||
val itemInfo = listState.layoutInfo.visibleItemsInfo
|
val layoutInfo = listState.layoutInfo
|
||||||
.firstOrNull { item ->
|
val itemsInfo = layoutInfo.visibleItemsInfo
|
||||||
offsetY >= item.offset && offsetY <= item.offset + item.size
|
if (itemsInfo.isEmpty()) return null
|
||||||
}
|
|
||||||
|
|
||||||
return itemInfo?.let { info ->
|
// 核心修复:校准坐标系。
|
||||||
items.getOrNull(info.index)?.let { item ->
|
// offsetY 是相对于此 Box 顶部的。
|
||||||
info.index to item
|
// 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) {
|
fun applySelection(id: ID, add: Boolean) {
|
||||||
val current = latestSelectedIds
|
val current = latestSelectedIds
|
||||||
onSelectionChange(
|
if (add) {
|
||||||
if (add) current + id else current - id
|
if (!current.contains(id)) {
|
||||||
)
|
latestOnSelectionChange(current + id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (current.contains(id)) {
|
||||||
|
latestOnSelectionChange(current - id)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier.pointerInput(Unit) {
|
modifier = modifier
|
||||||
coroutineScope {
|
.pointerInput(listState) {
|
||||||
launch {
|
detectTapGestures(
|
||||||
detectTapGestures(
|
onTap = { offset ->
|
||||||
onTap = { offset ->
|
findItemAtOffset(offset.y)?.let { (_, id) ->
|
||||||
findItemAtOffset(offset.y)?.let { (_, item) ->
|
applySelection(id, !latestSelectedIds.contains(id))
|
||||||
val id = idProvider(item)
|
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||||
applySelection(id, !latestSelectedIds.contains(id))
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.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)
|
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
if (change.pressed) change.consume()
|
||||||
}
|
},
|
||||||
|
onDragEnd = { lastProcessedIndex = -1 },
|
||||||
launch {
|
onDragCancel = { lastProcessedIndex = -1 }
|
||||||
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 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user