Cicero
07/17/2024, 9:23 AMCicero
07/17/2024, 9:24 AMimport android.util.Log
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Preview
@Composable
fun TestSlider() {
Sample()
}
@Composable
fun Sample() {
val height = 50.dp
val diameter = 50f
var x by remember { mutableFloatStateOf(0f) }
var selectedKnob by remember { mutableStateOf<Int?>(null) }
var primaryKnob by remember { mutableStateOf(100f) }
Log.i("Slider", "primaryKnob: $primaryKnob")
Column(
modifier = Modifier.fillMaxSize().height(height),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
BoxWithConstraints(
modifier = Modifier
) {
val boxWidth = this.constraints.maxWidth.toFloat()
Canvas(modifier = Modifier
.fillMaxWidth()
.height(height)
.pointerInput(Unit) {
this.detectTapGestures(onPress = {
Log.i("Slider", "x: ${it.x}, knobArea: ${primaryKnob - diameter..primaryKnob}")
when {
it.x in primaryKnob - diameter..primaryKnob + diameter -> {
x = primaryKnob
selectedKnob = 0
}
else -> selectedKnob = null
}
})
}
// .detectTapGestures(
// primaryKnob = primaryKnob,
// knobTouchArea = 100f,
// onSelectedKnobChange = { _x, _selectedKnob ->
// Log.i("Slider", "x: $_x, selectedKnob: $_selectedKnob")
// if (_x != null) {
// x = _x
// }
// selectedKnob = _selectedKnob
// }
// )
.pointerInput(Unit) {
this.detectDragGestures(
onDragStart = {
x = it.x
},
onDragEnd = {
when (selectedKnob) {
0 -> {
primaryKnob = x
}
}
},
onDragCancel = {},
onDrag = { pointerInputChange: PointerInputChange, offset: Offset ->
if (x < 0)
x = 0f
if (x > boxWidth)
x = boxWidth
if (selectedKnob != null) {
x += offset.x
}
when (selectedKnob) {
0 -> primaryKnob = x
}
})
}) {
this.drawCircle(
color = Color.Cyan, radius = diameter,
center = Offset(primaryKnob, this.center.y)
)
}
}
}
}
private fun Modifier.detectTapGestures(
primaryKnob: Float,
knobTouchArea: Float,
onSelectedKnobChange: (Float?, Int?) -> Unit
) = pointerInput(Unit) {
this.detectTapGestures(onPress = {
Log.i("Slider", "x: ${it.x}, knobArea: ${primaryKnob - knobTouchArea..primaryKnob}")
when {
it.x in primaryKnob - knobTouchArea..primaryKnob + knobTouchArea -> onSelectedKnobChange(primaryKnob, 0)
else -> onSelectedKnobChange(null, null)
}
})
}
Cicero
07/17/2024, 9:24 AMCicero
07/17/2024, 9:26 AMprimaryKnob
, from the perspective of detectTapGestures
(which receives and uses the primaryKnob
value), is not updated.Cicero
07/17/2024, 9:27 AM.pointerInput(Unit) {
this.detectTapGestures(onPress = {
Log.i("Slider", "x: ${it.x}, knobArea: ${primaryKnob - diameter..primaryKnob}")
when {
it.x in primaryKnob - diameter..primaryKnob + diameter -> {
x = primaryKnob
selectedKnob = 0
}
else -> selectedKnob = null
}
})
}
Is there a way to work with external and mutable parameters and modifiers?
what is happening here?Cicero
07/17/2024, 9:43 AMWhen you extract functions into Modifier extensions, the parameters used inside these functions are captured when the function is called, and they do not get updated if the state changes outside these functions. This is a common issue with Kotlin and Compose due to how lambdas and captured variables work.
To address this, we need to ensure that the state used within the modifier functions is correctly updated by using higher-order functions or by passing lambdas that will fetch the current state.
Cicero
07/17/2024, 9:43 AMimport android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Preview
@Composable
fun TestSlider() {
Sample()
}
@Composable
fun Sample() {
val height = 50.dp
val diameter = 50f
var x by remember { mutableFloatStateOf(0f) }
var selectedKnob by remember { mutableStateOf<Int?>(null) }
var primaryKnob by remember { mutableStateOf(100f) }
Log.i("Slider", "primaryKnob: $primaryKnob")
Column(
modifier = Modifier.fillMaxSize().height(height),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
BoxWithConstraints(
modifier = Modifier
) {
val boxWidth = this.constraints.maxWidth.toFloat()
Canvas(modifier = Modifier
.fillMaxWidth()
.height(height)
// .pointerInput(Unit) {
// this.detectTapGestures(onPress = {
// Log.i("Slider", "x: ${it.x}, knobArea: ${primaryKnob - diameter..primaryKnob}")
// when {
// it.x in primaryKnob - diameter..primaryKnob + diameter -> {
// x = primaryKnob
// selectedKnob = 0
// }
//
// else -> selectedKnob = null
// }
// })
// }
.detectTapGestures(
getPrimaryKnob = { primaryKnob },
onSelectedKnobChange = { _x, _selectedKnob ->
Log.i("Slider", "x: $_x, selectedKnob: $_selectedKnob")
if (_x != null) {
x = _x
}
selectedKnob = _selectedKnob
}
)
.pointerInput(Unit) {
this.detectDragGestures(
onDragStart = {
x = it.x
},
onDragEnd = {
when (selectedKnob) {
0 -> {
primaryKnob = x
}
}
},
onDragCancel = {},
onDrag = { pointerInputChange: PointerInputChange, offset: Offset ->
if (x < 0)
x = 0f
if (x > boxWidth)
x = boxWidth
if (selectedKnob != null) {
x += offset.x
}
when (selectedKnob) {
0 -> primaryKnob = x
}
})
}) {
this.drawCircle(
color = Color.Cyan, radius = diameter,
center = Offset(primaryKnob, this.center.y)
)
}
}
}
}
@SuppressLint("ModifierFactoryUnreferencedReceiver")
private fun Modifier.detectTapGestures(
getPrimaryKnob: () -> Float,
onSelectedKnobChange: (Float?, Int?) -> Unit
) = pointerInput(Unit) {
detectTapGestures(onPress = {
val primaryKnob = getPrimaryKnob()
when {
it.x in primaryKnob - 100..primaryKnob + 100 -> onSelectedKnobChange(primaryKnob, 0)
else -> onSelectedKnobChange(null, null)
}
})
}
Cicero
07/17/2024, 9:43 AM