我想显示一个滑块,它可以由用户通过拖放更新,也可以从服务器实时更新。
当用户完成拖动动作时,我想将最终值发送到服务器。
我最初的尝试是:
@Composable
fun LightView(
channel: UiChannel,
onDimmerChanged: (Float) -> Unit
) {
val sliderValue = channel....
Slider(value = sliderValue, onValueChange = onDimmerChanged)
}
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) }
)
}
它在应用端运行良好,值只发送一次,当用户停止拖动时。
但是,当服务器有更新时,它无法更新视图。我猜这与remember
有关。所以我尝试不使用remember
:
@Composable
fun Slider(initialValue: Float, valueSetter: (Float) -> Unit) {
var value by mutableStateOf(initialValue)
Slider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { valueSetter(value) }
)
}
这一次,当服务器更新值时,视图正确更新,但当用户拖动滑块时,视图不再移动。
我肯定我错过了什么与国家提升和所有,但我不知道是什么。
- 所以我最后一个问题**如何创建一个既可以由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
)
}
而且它也不起作用。当使用拖放时,sliderState
被正确地更新,并且onDimmerChanged()
被用正确的值调用。但是由于某种原因,当点击滑动(而不是滑动)时,valueSet
被调用,并且sliderState.value
不包含正确的值。我不明白这个值从哪里来的。
5条答案
按热度按时间kzmpq1sx1#
关于本地&服务器(或视图模型)状态相互冲突的原始问题:
我通过检测我们是否与滑块交互来解决这个问题:
正如你所说的,我们应该永远不要从
onValueChange
更新视图模型-因为这只是为了在本地更新滑块值(文档),而onValueChangeFinished
是用来在我们完成交互后立即将***当前本地状态***发送到视图模型的。关于电流相互作用的检测,我们可以利用
InteractionSource
。工作示例:
希望能有所帮助。
pjngdqdw2#
这里有几件事要做,所以让我们试着把它分解一下。
最初的尝试是每次更改值都调用
onDimmerChanged
,这看起来很棒!看一下第二次尝试,创建一个定制的
Slider
组件是可行的,但是有一些问题。让我们来讨论一下
Slider
组合中发生了什么:1.我们还记得
initialValue
的状态1.更新
channel
值,重新组合LightView
,Slider
也是如此1.因为我们记住了状态,所以它仍然设置为以前的
initialValue
你认为
remember
是罪魁祸首的想法是正确的。当记忆一个值时,除非我们告诉Compose,否则它在重新组合时不会更新。但是没有记忆(如第三次尝试所示),每次重组都会创建一个状态模型(var value by mutableStateOf(initialValue)
)。由于每次value
改变时都会触发重新组合,因此我们总是将initialValue
而不是更新后的值传递给Slider。使其永远不会根据此Composable中的更改进行更新。相反,我们希望将initialValue
作为键传递给remember
,告诉Compose在键更改时重新计算值。您可能也可以将
Slider
拉入您的LightView
:最后,关于@ commonsare建议的尝试。
这是另一种实现相同目的的方法,但是传递
MutableState
s是一种反模式,应该尽可能避免,这就是状态提升的作用所在(您在前面的尝试中尝试做的事情:)最后,关于
Slider
的onValueChangeFinished
使用了错误的值。而且它也不起作用。当使用拖放时,sliderState被正确更新,并且onDimmerChanged()被用正确的值调用。但是由于某种原因,当点击滑动(而不是滑动)时,valueSet被调用,并且sliderState.value不包含正确的值。我不明白这个值从哪里来。
这是
Slider
组件中的一个bug。您可以通过查看以下代码的输出来检查:由于
key
应该随着每次重新组合而改变,您可以看到onValueChangeFinished
回调包含了对旧组合的引用(希望我没有说错)。希望这有助于澄清一些事情!
dgjrabp23#
是的,我也可以重现你的问题。我在bug主题中回答了,但是我会复制粘贴到这里,这样其他人在匆忙的时候就可以修复它。不幸的是,这个解决方案需要复制整个Slider类。
问题在于,拖动和单击修改器使用不同的“位置”示例,尽管它们应该始终相同(因为“位置”是在“记住”中创建的)。
问题出在指针输入修饰符上。
将
Modifier.pointerInput()
更改为接受除Unit之外的任何其他主题,以便该修饰符中的Position
示例始终是最新的,并在重新创建Position
时进行更新。例如,可以将其更改为Modifier.pointerInput(valueRange)
w6lpcovy4#
我遇到了这个问题,正如jossiwolf所指出的,将进度作为
key
用于remember{}
是必要的,以确保Slider进度在重新组合后更新。我有一个额外的问题,虽然,在那里,如果我更新了进度中途寻找,滑块将重新组成,拇指将恢复到原来的位置。
为了解决这个问题,我使用了一个临时的滑块位置,它只在拖动过程中使用:
jgovgodb5#
我对Steffen的答案进行了一些详细的阐述,这样即使用户当前正在拖动滑块,也可以更新“external”“viewModel”值(因为有许多用例需要这样做)。
用法与合成“本机”Slider相同,不同之处在于onValueChangeFinished参数现在接受结束浮点值。