You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose多点触控状态实现求助:无法切换至11状态

问题描述

需要实现以下触摸状态逻辑:

00:无任何触摸
10:仅玩家1触摸
01:仅玩家2触摸
11:玩家1和玩家2同时触摸

同时需要变量记录首次触摸的玩家:P1表示玩家1先触摸,P2表示玩家2先触摸。
特殊规则:当状态为11(双方都触摸)时,若一方抬手,剩余触摸方自动成为首次触摸记录的对象。

场景示例:

  • 玩家1触摸 → 状态变为10,首次触摸记录为P1
  • 玩家2触摸 → 状态变为11
  • 玩家1抬手 → 状态变为01,首次触摸记录变为P2

当前问题:代码不支持多点触控,无法切换到11状态,现有代码如下:

import android.view.MotionEvent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.fabian.twov01.R
import com.fabian.twov01.datasource.DataSource
import androidx.compose.runtime.*

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun TwoPlayersScreen(navController: NavController) {

    var touchState by remember { mutableStateOf("00") } // "00", "10", "01", "11"
    var firstTouch by remember { mutableStateOf("") } // "", "P1", "P2"

    Box(modifier = Modifier.fillMaxSize()) {

//          Background Image
//        Image(
//            painter = painterResource(id = R.drawable.mainmenu_bg),
//            contentDescription = "Background Image",
//            modifier = Modifier.fillMaxSize(),
//            contentScale = ContentScale.Crop
//        )

        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(5.dp)
        ) {

            // LIFE, STATS, NAME Row
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .background(color = Color.Magenta)
                    .weight(.6f), // Adjust the weight as needed
                horizontalArrangement = Arrangement.SpaceBetween
            ) {

                //Player 1 stats
                Column(
                ) {
                    Row(
                    ) {
                        //PLAYER ICON
                        Image(
                            painter = painterResource(id = R.drawable.exit),
                            contentDescription = "Exit Icon",
                            modifier = Modifier
                                .size(50.dp),
                            contentScale = ContentScale.Fit
                        )

                        Column(

                        ) {
                            //LIFE
                            LinearProgressIndicator(

                                progress = 0.5f,
                                modifier = Modifier
                                    .padding(top = 1.dp)
                                    .height(25.dp),
                                color = Color.Green
                            )

                            Text(text = "Wins: 0")
                        }

                    }

                    Text(text = "Player 1")

                }

                //PAUSE AND PLAY
                Column(
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally,

                    ) {
                    Text(text = "2 Player Mode")

                    Image(
                        painter = painterResource(id = R.drawable.exit),
                        contentDescription = "Exit Icon",
                        modifier = Modifier
                            .size(50.dp),
                        contentScale = ContentScale.Fit
                    )

                    Text(text = "Best of 3")

                }


                //Player 2 Stats
                Column(
                    horizontalAlignment = Alignment.End
                ) {
                    Row(

                    ) {

                        Column(
                            horizontalAlignment = Alignment.End
                        ) {
                            //LIFE
                            LinearProgressIndicator(

                                progress = 0.5f,
                                modifier = Modifier
                                    .padding(top = 1.dp)
                                    .height(25.dp),
                                color = Color.Green
                            )

                            Text(text = "Wins: 0")
                        }

                        //PLAYER ICON
                        Image(
                            painter = painterResource(id = R.drawable.exit),
                            contentDescription = "Exit Icon",
                            modifier = Modifier
                                .size(50.dp),
                            contentScale = ContentScale.Fit
                        )

                    }

                    Text(text = "Player 1")
                }



            }





            // Assuming this is the "STATE OF THE HAND BY DRAWABLES" Row
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .background(color = Color.Gray)
                    .weight(1.4f) // Adjust the weight as needed
            ) {
                // Player 1 control area
                Box(
                    modifier = Modifier
                        .weight(1f)
                        .fillMaxHeight()
                        .background(Color.DarkGray)
                        .pointerInteropFilter { motionEvent ->
                            when (motionEvent.action) {
                                MotionEvent.ACTION_DOWN -> {
                                    touchState = if (touchState == "00" || touchState == "01") "10" else "11"
                                    if (firstTouch == "") firstTouch = "P1"
                                }
                                MotionEvent.ACTION_UP -> {
                                    touchState = if (touchState == "11") "01" else "00"
                                    if (firstTouch == "P1") firstTouch = if (touchState == "01") "P2" else ""
                                }
                            }
                            true
                        }
                )

                // Player 2 control area
                Box(
                    modifier = Modifier
                        .weight(1f)
                        .fillMaxHeight()
                        .background(Color.LightGray)
                        .pointerInteropFilter { motionEvent ->
                            when (motionEvent.action) {
                                MotionEvent.ACTION_DOWN -> {
                                    touchState = if (touchState == "00" || touchState == "10") "01" else "11"
                                    if (firstTouch == "") firstTouch = "P2"
                                }
                                MotionEvent.ACTION_UP -> {
                                    touchState = if (touchState == "11") "10" else "00"
                                    if (firstTouch == "P2") firstTouch = if (touchState == "10") "P1" else ""
                                }
                            }
                            true
                        }
                )

            }
            // Debug Text - Displaying the current touch state and first touch
            Text("Touch State: $touchState, First Touch: $firstTouch")
        }
    }



}
最佳解决方法

核心问题是**pointerInteropFilter不支持多点触控**,它会拦截所有触摸事件,导致两个Box无法同时接收触摸输入。改用Compose原生推荐的pointerInput配合触摸手势检测API,同时调整状态管理逻辑,单独跟踪每个玩家的触摸状态,避免全局状态推导的混乱。

关键优化点:

  1. 拆分触摸状态:用isPlayer1Touching和isPlayer2Touching两个独立状态分别记录玩家触摸状态,touchState由这两个状态实时推导,避免逻辑冲突。
  2. 替换触摸事件处理API:使用pointerInput替代pointerInteropFilter,它原生支持多点触摸,不会阻止其他组件接收触摸事件。
  3. 优化首次触摸记录逻辑:
    • 初始状态下,第一个触发触摸的玩家设置为firstTouch
    • 当双方都触摸后,若某一方抬手,将剩余触摸的玩家设为firstTouch
    • 所有触摸结束时,清空firstTouch

修改后的完整代码:

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.fabian.twov01.R
import androidx.compose.ui.input.pointer.detectTapGestures

@Composable
fun TwoPlayersScreen(navController: NavController) {
    // 单独跟踪每个玩家的触摸状态
    var isPlayer1Touching by remember { mutableStateOf(false) }
    var isPlayer2Touching by remember { mutableStateOf(false) }
    var firstTouch by remember { mutableStateOf("") }

    // 实时推导全局触摸状态
    val touchState by derivedStateOf {
        when {
            isPlayer1Touching && isPlayer2Touching -> "11"
            isPlayer1Touching -> "10"
            isPlayer2Touching -> "01"
            else -> "00"
        }
    }

    Box(modifier = Modifier.fillMaxSize()) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(5.dp)
        ) {
            // LIFE, STATS, NAME Row
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .background(Color.Magenta)
                    .weight(.6f),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                // Player 1 stats
                Column {
                    Row {
                        Image(
                            painter = painterResource(id = R.drawable.exit),
                            contentDescription = "Exit Icon",
                            modifier = Modifier.size(50.dp),
                            contentScale = ContentScale.Fit
                        )
                        Column {
                            LinearProgressIndicator(
                                progress = 0.5f,
                                modifier = Modifier
                                    .padding(top = 1.dp)
                                    .height(25.dp),
                                color = Color.Green
                            )
                            Text(text = "Wins: 0")
                        }
                    }
                    Text(text = "Player 1")
                }

                // PAUSE AND PLAY
                Column(
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    Text(text = "2 Player Mode")
                    Image(
                        painter = painterResource(id = R.drawable.exit),
                        contentDescription = "Exit Icon",
                        modifier = Modifier.size(50.dp),
                        contentScale = ContentScale.Fit
                    )
                    Text(text = "Best of 3")
                }

                // Player 2 Stats
                Column(horizontalAlignment = Alignment.End) {
                    Row {
                        Column(horizontalAlignment = Alignment.End) {
                            LinearProgressIndicator(
                                progress = 0.5f,
                                modifier = Modifier
                                    .padding(top = 1.dp)
                                    .height(25.dp),
                                color = Color.Green
                            )
                            Text(text = "Wins: 0")
                        }
                        Image(
                            painter = painterResource(id = R.drawable.exit),
                            contentDescription = "Exit Icon",
                            modifier = Modifier.size(50.dp),
                            contentScale = ContentScale.Fit
                        )
                    }
                    Text(text = "Player 2") // 修复原代码中的文本错误
                }
            }

            // 触摸控制区域
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .background(Color.Gray)
                    .weight(1.4f)
            ) {
                // 玩家1触摸区域
                Box(
                    modifier = Modifier
                        .weight(1f)
                        .fillMaxHeight()
                        .background(Color.DarkGray)
                        .pointerInput(Unit) {
                            detectTapGestures(
                                onPress = {
                                    isPlayer1Touching = true
                                    // 设置首次触摸
                                    if (firstTouch.isEmpty() && !isPlayer2Touching) {
                                        firstTouch = "P1"
                                    }
                                    // 等待触摸结束
                                    tryAwaitRelease()
                                    isPlayer1Touching = false
                                    // 处理抬手后的首次触摸更新
                                    when {
                                        isPlayer2Touching -> firstTouch = "P2"
                                        else -> firstTouch = ""
                                    }
                                }
                            )
                        }
                )

                // 玩家2触摸区域
                Box(
                    modifier = Modifier
                        .weight(1f)
                        .fillMaxHeight()
                        .background(Color.LightGray)
                        .pointerInput(Unit) {
                            detectTapGestures(
                                onPress = {
                                    isPlayer2Touching = true
                                    // 设置首次触摸
                                    if (firstTouch.isEmpty() && !isPlayer1Touching) {
                                        firstTouch = "P2"
                                    }
                                    // 等待触摸结束
                                    tryAwaitRelease()
                                    isPlayer2Touching = false
                                    // 处理抬手后的首次触摸更新
                                    when {
                                        isPlayer1Touching -> firstTouch = "P1"
                                        else -> firstTouch = ""
                                    }
                                }
                            )
                        }
                )
            }

            // 调试文本
            Text(
                text = "Touch State: $touchState, First Touch: $firstTouch",
                modifier = Modifier.padding(top = 10.dp)
            )
        }
    }
}

代码说明:

  • 使用derivedStateOf实时计算touchState,确保状态始终与两个玩家的触摸状态同步。
  • detectTapGestures的onPress回调会在触摸按下时触发,tryAwaitRelease()会等待触摸抬起后执行后续逻辑,完美匹配触摸生命周期。
  • 修复了原代码中玩家2区域显示"Player 1"的文本错误。
  • 完全支持多点触摸,两个玩家可以同时触摸各自区域,状态会正确切换到11。

内容的提问来源于stack exchange,提问作者Javanese Python

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 14:49:51