compose 三指滑動(dòng)demo

外部使用

setContent {
            Box(modifier = Modifier.fillMaxSize()){
                ThreePointParent(modifier = Modifier.align(Alignment.Center))
            }

        }
package com.example.composetest.threePoint

import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import android.util.Log // 導(dǎo)入 Log 庫

private const val TAG = "RealTimeDrag"

/**
 * 存儲(chǔ)可拖拽子項(xiàng)的狀態(tài)
 */
data class DraggableItem(
    val id: Int,
    val originalIndex: Int,
    var currentOrder: Int,
    var offsetPx: Float = 0f, // 視覺偏移量,用于讓位/歸位動(dòng)畫目標(biāo)
    var zIndex: Float = 0f    // Z軸深度,用于拖拽時(shí)懸浮
)

@Composable
fun ThreePointParent(modifier: Modifier = Modifier) {
    Box(
        modifier = modifier
            .width(1500.dp)
            .height(800.dp)
            .background(Color.Gray.copy(alpha = 0.5f)),
        contentAlignment = Alignment.Center
    ) {
        ThreeFingerDraggableRow(itemCount = 3) { index ->
            ThreePointView(index)
        }
    }
}

@Composable
fun ThreeFingerDraggableRow(
    itemCount: Int,
    content: @Composable (index: Int) -> Unit
) {
    val itemList = remember {
        mutableStateListOf<DraggableItem>().apply {
            repeat(itemCount) { index ->
                add(DraggableItem(id = index, originalIndex = index, currentOrder = index))
            }
        }
    }

    var draggingItemRef by remember { mutableStateOf<DraggableItem?>(null) }
    var rowWidth by remember { mutableStateOf(0) }

    var currentDragOffsetX by remember { mutableFloatStateOf(0f) }
    var logicalOrderOffset by remember { mutableIntStateOf(0) }

    val itemWidthPx = if (rowWidth > 0 && itemCount > 0) (rowWidth.toFloat() / itemCount) else 0f

    // --- 輔助 Lambda 函數(shù) (添加 Log) ---

    val startDrag: (DraggableItem) -> Unit = { item ->
        draggingItemRef = item
        currentDragOffsetX = 0f
        logicalOrderOffset = 0
        item.zIndex = 1f
        Log.d(TAG, "DragStart: Item ${item.id} (${item.currentOrder}) - Three fingers recognized.")
    }

    val endDrag: (DraggableItem?) -> Unit = { item ->
        val isCancelled = (item == null) // 通過傳入 null 判斷是否是 Cancel
        if (isCancelled) {
            Log.d(TAG, "DragCancel: Item reset.")
        } else {
            item?.let { dragItem ->
                val finalRelativeOffset = currentDragOffsetX - (logicalOrderOffset * itemWidthPx)
                dragItem.offsetPx = finalRelativeOffset
                dragItem.offsetPx = 0f
                dragItem.zIndex = 0f
                Log.d(TAG, "DragEnd: Item ${dragItem.id} settled. FinalOffset=${finalRelativeOffset}")
            }
        }
        currentDragOffsetX = 0f
        logicalOrderOffset = 0
        draggingItemRef = null
    }

    // --- Row 布局 ---

    Row(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight()
            .background(Color.DarkGray)
            .onGloballyPositioned { coordinates ->
                rowWidth = coordinates.size.width
            }
    ) {
        itemList.sortedBy { it.currentOrder }.forEach { item ->
            val isDragging = item == draggingItemRef

            val targetOffset = item.offsetPx

            val animatedOffsetX by animateFloatAsState(
                targetValue = targetOffset,
                animationSpec = tween(durationMillis = 3000),
                label = "ItemOffsetAnimation"
            )

            val dragModifier = Modifier.pointerInput(item) {
                awaitPointerEventScope {
                    while (true) {
                        // 1. 等待首次按下事件
                        val down = awaitFirstDown()
                        Log.v(TAG, "Pointer Down Detected. ID: ${down.id}")

                        // 2. *** 關(guān)鍵修改:循環(huán)等待三指按下 ***
                        var event = currentEvent
                        var touchCount = event.changes.filter { it.pressed }.size

                        // 循環(huán)等待,直到觸摸點(diǎn)達(dá)到 3 個(gè),或者所有手指抬起
                        while (touchCount < 3) {
                            val nextEvent = awaitPointerEvent()
                            event = nextEvent // 更新事件
                            touchCount = nextEvent.changes.filter { it.pressed }.size
                            Log.v(TAG, "Waiting for 3 fingers. Current Count: ${touchCount}")

                            // 如果所有手指都抬起了,跳出等待,重新開始
                            if (touchCount == 0) break
                        }

                        Log.v(TAG, "Final Check Touch Count: ${touchCount}")

                        if (touchCount >= 3) {
                            // 3. 三指識(shí)別成功,啟動(dòng)拖拽
                            startDrag(item)

                            // 4. 進(jìn)入拖拽循環(huán)
                            var dragCanceled = false

                            try {
                                do {
                                    // 獲取下一個(gè)指針事件
                                    event = awaitPointerEvent()

                                    // 找到第一個(gè)按下并移動(dòng)的指針 (ID 必須與 down.id 匹配)
                                    val dragPointer = event.changes.firstOrNull { it.id == down.id && it.pressed }

                                    if (dragPointer != null) {
                                        val movement = dragPointer.positionChange()
                                        dragPointer.consume()

                                        if (movement != Offset.Zero) {
                                            val dragAmountX = movement.x

                                            // --- 拖拽邏輯 ---
                                            draggingItemRef?.let { dragItem ->
                                                currentDragOffsetX += dragAmountX

                                                val relativeOffset = currentDragOffsetX - (logicalOrderOffset * itemWidthPx)

                                                val correctionDeltaOrder = checkAndSwapRealTime(
                                                    dragItem,
                                                    itemList,
                                                    itemWidthPx,
                                                    relativeOffset
                                                )

                                                if (correctionDeltaOrder != 0) {
                                                    logicalOrderOffset += correctionDeltaOrder
                                                    Log.d(TAG, "SWAP: Item ${dragItem.id} swapped. NewOrder=${dragItem.currentOrder}, NewLogicalOffset=${logicalOrderOffset}")
                                                }
                                                Log.v(TAG, "Dragging: currentCx=$currentDragOffsetX, relativeOffset=$relativeOffset")
                                            }
                                            // --- 拖拽邏輯結(jié)束 ---
                                        }
                                    }

                                    // 消耗所有未被處理的事件
                                    event.changes.forEach { if (!it.isConsumed) it.consume() }

                                    // 拖拽持續(xù)條件:至少有一個(gè)指針仍在按下
                                } while (event.changes.any { it.pressed })

                            } catch (e: Exception) {
                                Log.e(TAG, "Drag loop interrupted.", e)
                                dragCanceled = true
                            } finally {
                                if (dragCanceled) {
                                    endDrag(null) // onDragCancel
                                } else {
                                    endDrag(item) // onDragEnd
                                }
                            }

                        } else {
                            // 觸摸點(diǎn)數(shù)不足 3 個(gè),或已抬起,消耗事件并等待下一個(gè) down 事件
                            event.changes.forEach { if (!it.isConsumed) it.consume() }
                        }
                    }
                }
            }
            // ... (Item 渲染 Box 保持不變)
            Box(
                modifier = Modifier
                    .weight(1f)
                    .fillMaxHeight()
                    .zIndex(item.zIndex)
                    .then(dragModifier)
                    .graphicsLayer {
                        translationX = when {
                            isDragging -> currentDragOffsetX
                            else -> animatedOffsetX
                        }
                    },
                contentAlignment = Alignment.Center
            ) {
                content(item.originalIndex)
            }
        }
    }
}

// ... (checkAndSwapRealTime 和 ThreePointView 保持不變)

fun checkAndSwapRealTime(
    draggingItem: DraggableItem,
    itemList: SnapshotStateList<DraggableItem>,
    itemWidthPx: Float,
    relativeOffset: Float
): Int {
    if (itemWidthPx == 0f) return 0

    val currentOrder = draggingItem.currentOrder
    val halfWidth = itemWidthPx / 2f

    var correctionDeltaOrder = 0

    // --- 向右拖拽 ---
    if (relativeOffset > halfWidth && currentOrder < itemList.size - 1) {
        val neighbor = itemList.find { it.currentOrder == currentOrder + 1 }
        if (neighbor != null) {
            neighbor.offsetPx = -itemWidthPx
            neighbor.currentOrder = currentOrder
            draggingItem.currentOrder = currentOrder + 1
            neighbor.offsetPx = 0f
            correctionDeltaOrder = 1
        }
    }

    // --- 向左拖拽 ---
    else if (relativeOffset < -halfWidth && currentOrder > 0) {
        val neighbor = itemList.find { it.currentOrder == currentOrder - 1 }
        if (neighbor != null) {
            neighbor.offsetPx = itemWidthPx
            neighbor.currentOrder = currentOrder
            draggingItem.currentOrder = currentOrder - 1
            neighbor.offsetPx = 0f
            correctionDeltaOrder = -1
        }
    }

    return correctionDeltaOrder
}


@Composable
fun ThreePointView(index: Int){
    val colors = listOf(Color.Red, Color.Green, Color.Blue)
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(colors.getOrElse(index) { Color.Gray })
        ,
        contentAlignment = Alignment.Center
    ){
        Text(
            text = "第${index}個(gè)",
            fontSize = 30.sp,
            color = Color.White
        )
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewDraggableRow() {
    ThreePointParent()
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容