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,同时调整状态管理逻辑,单独跟踪每个玩家的触摸状态,避免全局状态推导的混乱。
关键优化点:
- 拆分触摸状态:用
isPlayer1Touching和isPlayer2Touching两个独立状态分别记录玩家触摸状态,touchState由这两个状态实时推导,避免逻辑冲突。 - 替换触摸事件处理API:使用
pointerInput替代pointerInteropFilter,它原生支持多点触摸,不会阻止其他组件接收触摸事件。 - 优化首次触摸记录逻辑:
- 初始状态下,第一个触发触摸的玩家设置为
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
相关产品推荐
相关产品推荐

