我不认为只有一种最佳实践,而是要 Select 适合您需求的方法.
您应该决定当您的应用程序处于活动状态时,您的ViewModel是否需要存储在内存中,或者是否需要将其范围限定为导航图或Composable.
第二件要考虑的事情是,你是否会在另一个屏幕或另一个应用程序中使用相同的Composable.如果是这样的话,您可以考虑通过回调将状态作为参数和事件传递给ViewModel,而不是将ViewModel传递给Composable.
而不是这个
fun Input(optionData: OptionData,viewModel: InputViewModel = viewModel()) {
InputItem(viewModel)
}
如果我需要使用,或者我认为我将来会在不同的部分或其他应用程序中使用Input
,我倾向于使用这个
fun Input(optionData: OptionData, someOtherData, onOptionDataChanged:()->Unit, onSomeOtherDataChanged: () -> Unit) {
InputItem(viewModel)
}
关于这个主题,State in jetpack Compose from codelabs是一篇很好的阅读文章.
@Composable
fun WellnessScreen(
modifier: Modifier = Modifier,
wellnessViewModel: WellnessViewModel = viewModel()
) {
Column(modifier = modifier) {
StatefulCounter()
WellnessTasksList(
list = wellnessViewModel.tasks,
onCheckedTask = { task, checked ->
wellnessViewModel.changeTaskChecked(task, checked)
},
onCloseTask = { task ->
wellnessViewModel.remove(task)
}
)
}
}
@Composable
fun WellnessTasksList(
list: List<WellnessTask>,
onCheckedTask: (WellnessTask, Boolean) -> Unit,
onCloseTask: (WellnessTask) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(
modifier = modifier
) {
items(
items = list,
key = { task -> task.id }
) { task ->
WellnessTaskItem(
taskName = task.label,
checked = task.checked,
onCheckedChange = { checked -> onCheckedTask(task, checked) },
onClose = { onCloseTask(task) }
)
}
}
}
@Composable
fun WellnessTaskItem(
taskName: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
onClose: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier, verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
text = taskName
)
Checkbox(
checked = checked,
onCheckedChange = onCheckedChange
)
IconButton(onClick = onClose) {
Icon(Icons.Filled.Close, contentDescription = "Close")
}
}
}
最后但并非最不重要的一点是,如果它是不依赖于任何业务逻辑的UI逻辑,或者如果您正在构建独立的定制可组合组件作为定制视图的对应物,您可以考虑在包装在remember
中的类中捕获UI逻辑,而不是在ViewModel中.
这种方法的例子是我们在列表、脚手架和其他默认可组合项中使用的任何reemory X函数.
例如,remmeberScrollState
个
@Stable
class ScrollState(initial: Int) : ScrollableState {
/**
* current scroll position value in pixels
*/
var value: Int by mutableStateOf(initial, structuralEqualityPolicy())
private set
/**
* maximum bound for [value], or [Int.MAX_VALUE] if still unknown
*/
var maxValue: Int
get() = _maxValueState.value
internal set(newMax) {
_maxValueState.value = newMax
if (value > newMax) {
value = newMax
}
}
/**
* [InteractionSource] that will be used to dispatch drag events when this
* list is being dragged. If you want to know whether the fling (or smooth scroll) is in
* progress, use [isScrollInProgress].
*/
val interactionSource: InteractionSource get() = internalInteractionSource
internal val internalInteractionSource: MutableInteractionSource = MutableInteractionSource()
private var _maxValueState = mutableStateOf(Int.MAX_VALUE, structuralEqualityPolicy())
/**
* We receive scroll events in floats but represent the scroll position in ints so we have to
* manually accumulate the fractional part of the scroll to not completely ignore it.
*/
private var accumulator: Float = 0f
private val scrollableState = ScrollableState {
val absolute = (value + it + accumulator)
val newValue = absolute.coerceIn(0f, maxValue.toFloat())
val changed = absolute != newValue
val consumed = newValue - value
val consumedInt = consumed.roundToInt()
value += consumedInt
accumulator = consumed - consumedInt
// Avoid floating-point rounding error
if (changed) consumed else it
}
override suspend fun scroll(
scrollPriority: MutatePriority,
block: suspend ScrollScope.() -> Unit
): Unit = scrollableState.scroll(scrollPriority, block)
override fun dispatchRawDelta(delta: Float): Float =
scrollableState.dispatchRawDelta(delta)
override val isScrollInProgress: Boolean
get() = scrollableState.isScrollInProgress
/**
* Scroll to position in pixels with animation.
*
* @param value target value in pixels to smooth scroll to, value will be coerced to
* 0..maxPosition
* @param animationSpec animation curve for smooth scroll animation
*/
suspend fun animateScrollTo(
value: Int,
animationSpec: AnimationSpec<Float> = SpringSpec()
) {
this.animateScrollBy((value - this.value).toFloat(), animationSpec)
}
/**
* Instantly jump to the given position in pixels.
*
* Cancels the currently running scroll, if any, and suspends until the cancellation is
* complete.
*
* @see animateScrollTo for an animated version
*
* @param value number of pixels to scroll by
* @return the amount of scroll consumed
*/
suspend fun scrollTo(value: Int): Float = this.scrollBy((value - this.value).toFloat())
companion object {
/**
* The default [Saver] implementation for [ScrollState].
*/
val Saver: Saver<ScrollState, *> = Saver(
save = { it.value },
restore = { ScrollState(it) }
)
}
}
额外的
此外,根据您的需要或适用性,使用Modifier
比Composable
的州可能会使它更容易与其他Composasble一起使用.
例如
class MyState(val color:Color)
@composable
fun rememberMyState(color:Color) = remember{MyState(color)}
在修饰符中包装UI逻辑
fun Modifier.myModifier(myState:State)= this.then(
Modifier.color(myState.color)
)
在某些情况下,可重用性可能比可组合性更高
@Composable
fun MyComposable(myState: MyState) {
Column(Modifier.background(color){...}
}
如果您在上面的示例中使用Composable
,我们将布局限制为Column
,而您可以使用第一个布局,您可以使用任何Composable
.实现主要取决于您的喜好.