Jon*_*nas 10 android kotlin android-jetpack-compose
我想显示一个滑块,该滑块可以由用户使用拖放进行更新,也可以从服务器实时更新。
当用户完成拖动手势时,我想将最终值发送到服务器。
我最初的尝试是:
@Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
val sliderValue = channel....
Slider(value = sliderValue, onValueChange = onDimmerChanged)
}
Run Code Online (Sandbox Code Playgroud)
该onDimmerChanged方法来自我的 ViewModel,它更新服务器值。它工作得很好,但是onValueChange每次移动都会被调用,这会用不需要的请求轰炸服务器。
我尝试创建一个自定义滑块:
@Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
val sliderValue = channel....
Slider(initialValue = sliderValue, valueSetter = onDimmerChanged)
}
@Composable
fun Slider(initialValue: Float, valueSetter: (Float) -> Unit) {
var value by remember { mutableStateOf(initialValue) }
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { valueSetter(value) }
)
}
Run Code Online (Sandbox Code Playgroud)
它在应用程序端运行良好,并且该值仅在用户停止拖动时发送一次。
但是,当服务器有更新时,它无法更新视图。我猜这与remember. 所以我尝试不使用remember:
@Composable
fun Slider(initialValue: Float, valueSetter: (Float) -> Unit) {
var value by mutableStateOf(initialValue)
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { valueSetter(value) }
)
}
Run Code Online (Sandbox Code Playgroud)
这次,当服务器更新值时,视图会正确更新,但当用户拖动滑块时,视图不再移动。
我确信我错过了一些与状态提升有关的东西,但我不知道是什么。
所以我的最后一个问题是:如何创建一个可由 ViewModel 或用户更新的 Slider,并仅在用户完成拖动时通知 ViewModel 新值?
编辑:
我还尝试了@CommonsWare 的建议:
@Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
val sliderValue = channel....
val sliderState = mutableStateOf(sliderValue)
Slider(state = sliderState, valueSet = { onDimmerChanged(sliderState.value) })
}
@Composable
fun Slider(state: MutableState<Float>, valueSet: () -> Unit) {
Slider(
value = state.value,
onValueChange = { state.value = it },
onValueChangeFinished = valueSet
)
}
Run Code Online (Sandbox Code Playgroud)
而且它也不起作用。使用拖放时,sliderState会正确更新,并onDimmerChanged()使用正确的值进行调用。但由于某种原因,当点击滑动(而不是滑动)时,valueSet被调用并且sliderState.value不包含正确的值。我不明白这个值从哪里来。
关于本地和服务器(或视图模型)状态相互冲突的原始问题:
我通过检测我们是否正在与滑块交互来解决这个问题:
正如您所说,我们永远不应该从 - 更新视图模型onValueChange,因为这仅用于本地更新滑块值(文档)。相反,一旦我们完成交互,onValueChangeFinished就用于将当前本地状态发送到视图模型。
关于当前交互的检测,我们可以利用InteractionSource.
@Composable
fun SliderDemo() {
// In this demo, ViewModel updates its progress periodically from 0f..1f
val viewModel by remember { mutableStateOf(SliderDemoViewModel()) }
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// local slider value state
var sliderValueRaw by remember { mutableStateOf(viewModel.progress) }
// getting current interaction with slider - are we pressing or dragging?
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val isDragged by interactionSource.collectIsDraggedAsState()
val isInteracting = isPressed || isDragged
// calculating actual slider value to display
// depending on wether we are interacting or not
// using either the local value, or the ViewModels / server one
val sliderValue by derivedStateOf {
if (isInteracting) {
sliderValueRaw
} else {
viewModel.progress
}
}
Slider(
value = sliderValue, // using calculated sliderValue here from above
onValueChange = {
sliderValueRaw = it
},
onValueChangeFinished = {
viewModel.updateProgress(sliderValue)
},
interactionSource = interactionSource
)
// Debug interaction info
Text("isPressed: ${isPressed} | isDragged: ${isDragged}")
}
}
Run Code Online (Sandbox Code Playgroud)
希望有帮助。
这里发生了一些事情,所以让我们尝试将其分解。
每次值更改都被要求的最初尝试onDimmerChanged看起来很棒!
看看第二次尝试,创建自定义Slider组件是可行的,但存在一些问题。
@Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
val sliderValue = channel....
Slider(initialValue = sliderValue, valueSetter = onDimmerChanged)
}
@Composable
fun Slider(initialValue: Float, valueSetter: (Float) -> Unit) {
var value by remember { mutableStateOf(initialValue) }
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { valueSetter(value) }
)
}
Run Code Online (Sandbox Code Playgroud)
让我们讨论一下Slider可组合项中发生的情况:
initialValuechannel被更新、LightView被重组,也是如此SliderinitialValueremember你认为自己是这里的罪魁祸首的想法是正确的。当记住一个值时,除非我们告诉 Compose,否则在重组时它不会被更新。但是如果没有记忆(如您的第三次尝试所示),每次var value by mutableStateOf(initialValue)使用initialValue. 由于每次更改都会触发重组value,因此我们始终将 而不是更新后的值传递initialValue给 Slider,导致它永远不会通过此可组合项中的更改进行更新。相反,我们希望将initialValue其作为键传递给remember,告诉 Compose 在键发生变化时重新计算该值。
@Composable
fun Slider(initialValue: Float, valueSetter: (Float) -> Unit) {
var value by remember { mutableStateOf(initialValue) }
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { valueSetter(value) }
)
}
Run Code Online (Sandbox Code Playgroud)
您也可以将其拉Slider入您的LightView:
@Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
var value by remember(channel.sliderValue) { mutableStateOf(channel.sliderValue) }
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { onDimmerChanged(value) }
)
}
Run Code Online (Sandbox Code Playgroud)
最后,关于@commonsware 建议的尝试。
@Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
val sliderValue = channel....
val sliderState = mutableStateOf(sliderValue)
Slider(state = sliderState, valueSet = { onDimmerChanged(sliderState.value) })
}
@Composable
fun Slider(state: MutableState<Float>, valueSet: () -> Unit) {
Slider(
value = state.value,
onValueChange = { state.value = it },
onValueChangeFinished = valueSet
)
}
Run Code Online (Sandbox Code Playgroud)
这是做同样事情的另一种方式,但传递MutableStates 是一种反模式,如果可能的话应该避免。这就是状态提升有帮助的地方(您在之前的尝试中尝试做的事情:))
最后,关于Slider'sonValueChangeFinished使用错误的值。
而且它也不起作用。使用拖放时,sliderState 会正确更新,并且使用正确的值调用 onDimmerChanged()。但由于某种原因,当点击滑动(而不是滑动)时,调用 valueSet 并且 sliderState.value 不包含正确的值。我不明白这个值从哪里来。
这是组件中的一个错误Slider。您可以通过查看此代码的输出来检查这一点:
@Composable
fun Slider(initialValue: Float, valueSetter: (Float) -> Unit) {
var value by remember { mutableStateOf(initialValue) }
val key = Random.nextInt()
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = {
valueSetter(value)
println("Callback invoked. Current key: $key")
}
)
}
Run Code Online (Sandbox Code Playgroud)
由于key每次重新组合都会发生变化,因此您可以看到回调onValueChangeFinished包含对旧组合的引用(希望我说得对)。所以你没有发疯,这不是你的错:)
希望这有助于澄清一些事情!
| 归档时间: |
|
| 查看次数: |
6955 次 |
| 最近记录: |