Jetpack Compose:将数据从惰性列传递到另一个可组合项
Jetpack Compose: passing data from lazy column into another composable
希望你们一切都好。
我是 Kotlin 的新手,所以请耐心等待。
我在jetpack compose中创建了一个页面,如下图所示,它需要装饰但至少我想实现功能。
Simple app interface with lazy column at the left and data display at the right.
左侧是惰性列,显示数组列表中的项目列表。
惰性列中有可点击的卡片。
我需要的是,一旦用户单击列中的项目,其余项目详细信息就会显示在右侧的第二个可组合项中。
代码如下所示
@Composable
fun Greeting() {
Row(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.width(150.dp)
.background(color = Color.LightGray)
) {
items(photoList) { item ->
ProfileCard(photo = item) { <--- i need the item into the other composable.
// photoContent(
// id = item.id,
// owner = item.owner,
// secret = item.secret,
// server = item.server,
// farm = item.farm,
// title = item.title,
// ispublic = item.ispublic,
// isfriend = item.isfriend,
// isfamily = item.isfamily,
// url_s = item.url_s,
// height_s = item.height_s,
// width_s = item.width_s
// )
}
}
}
photoContent(
id = "",
owner = "",
secret = "",
server = "",
farm = 0,
title = "",
ispublic = 0,
isfamily = 0,
isfriend = 0,
url_s = "",
height_s = 0,
width_s = 0
)
}
}
此点击功能来自显示的卡片
@Composable
fun ProfileCard(photo: photo, clickAction: () -> Unit) {
Card( //We changed the default shape of Card to make a cut in the corner.
modifier = Modifier
.padding(top = 4.dp, bottom = 4.dp, start = 16.dp, end = 16.dp)
.fillMaxWidth()
.wrapContentHeight(
align = Alignment.Top
)
.clickable { clickAction.invoke() }, //<-- moving the action to main composable.
elevation = 8.dp,
backgroundColor = Color.White // Uses Surface color by default, so we have to override it.
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
ProfileContent(photo, Alignment.Start)
}
}
}
我尝试了几种方法将数据传递到另一个可组合项中,但我总是收到错误消息:
@composable invocations can only happen from the context of an @composable function
而且我无法删除 @Composable
注释,因为它具有可组合的内容...
如何通过这种方式传递数据?或者没有导航是不可能的?但这需要用户打开另一个页面而不是当前页面...
基本上,您需要为您的选择创建一个状态。
看这个例子:
假设这个 class 是你的 Photo
class...
data class MyUser(
val name: String,
val surname: String
)
然后,像这样定义屏幕:
@Composable
fun MyScreen(users: List<MyUser>) {
// This state will control the selection
var selectedUser by remember {
mutableStateOf<MyUser?>(null)
}
Row(Modifier.fillMaxSize()) {
LazyColumn(
Modifier
.weight(.3f)
.background(Color.Gray)) {
items(users) {
// This would be your ProfileCard
Text(
text = "${it.name} - ${it.surname}",
modifier = Modifier
.clickable {
// when you click on an item,
// the state is updated
selectedUser = it
}
.padding(16.dp)
)
}
}
Column(Modifier.weight(.7f)) {
// if the selection is not null, display it...
// this would be your PhotoContent
selectedUser?.let {
Text(it.name)
Text(it.surname)
}
}
}
}
像这样调用这个函数...
@Composable
fun Greeting() {
// Just a fake user list...
val users = (1..50).map { MyUser("User $it", "Surname $it") }
MyScreen(users = users)
}
会这样
听着,孩子,你需要在这里改变你的思维方式。
好的,让我们深入研究
Jetpack Compose 的核心是状态,状态以变量的形式(主要)保存。如果一个变量在很多地方被用作状态,你必须确保它被很好地维护。不允许 willy-nilly 修改和读取。因此,存储状态的正确方法是在 ViewModel
.
中
所以通过创建一个像
这样的虚拟机来启动
class ProfileViewModel : ViewModel() {
/*This variable shall store the photo,
Which I plan to use as an ID for the Profile Card.*/
var selectedProfile by mutableStateOf(Photo(/*Default Arguments*/)) //Store state as mutableStateOf to ensure proper recompostions
private set //Do not allow external modification, i.e., outside the ViewModel
//We'll be modifying the variable through this method
fun onSelectProfile(photo: Photo) {
selectedProfile = Photo
}
}
这只是一个简单的演示,所以我将视图模型限制为这个。
继续 activity,我们初始化视图模型
imports ...
class MainActivity : ComponentActivity() {
private val profileViewModel by viewModels<ProfileViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Greeting(
selectedProfile = profileViewModel.selectedProfile
onSelectProfile = profileViewModel::onSelectProfile
)
}
很明显,我对您的 Greeting Composable 进行了一些修改。让我们看看它们是什么:-
我基本上添加了两个参数。现在学习这个。在任何需要修改存储在使用它的地方以外的地方的状态,并且有时还需要修改它的情况下,您必须将两个参数传递给可组合项 - 一个用于读取值,一个用于写入值.有道理不是吗。这就是我在这里所做的。
我们将值从存储位置向下传递,将其提供给可组合项。为了允许可组合项触发修改,我们传递了一个 onChange
参数,该参数一直链接到存储位置。在本例中,我们将您的 Greeting Composable 的 onSelectProfile
链接到视图模型中定义的 onSelectProfile
。因此,onSelectProfile
的调用者是 Greeting Composable,但最终在视图模型内部调用了该方法,因为它向上传递。执行视图模型的 onSelectProfile
时, selectedProfile
变量会更新(请参阅视图模型中的方法,它会更新变量)。现在,可组合项正在读取变量,因此,可组合项本身会获取变量的更新值(因为我们使用的是 mutableStateOf
,因此触发了重组)。
因此,看看 Composable
@Composable
fun Greeting(
selectedProfile: Photo,
onSelectProfile: (photo: Photo) -> Unit
) {
Row(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.width(150.dp)
.background(color = Color.LightGray)
) {
items(photoList) { item ->
ProfileCard(photo = item, clickAction = onSelectProfile(item)) //We pass the photo like that
}
}
photoContent(
id = selectedProfile.id,
owner = selectedProfile.,
secret = selectedProfile.<respectiveProperty>,
server = selectedProfile.<respectiveProperty>,
farm = selectedProfile.<respectiveProperty>,
title = selectedProfile.<respectiveProperty>,
ispublic = selectedProfile.<respectiveProperty>,
isfamily = selectedProfile.<respectiveProperty>,
isfriend = selectedProfile.<respectiveProperty>,
url_s = selectedProfile.<respectiveProperty>,
height_s = selectedProfile.<respectiveProperty>,
width_s = selectedProfile.<respectiveProperty>
)
}
}
你看这就是你本质上一直在做的事情。您创建了一个 clickAction()
作为参数,不是吗?你正在经历同样的过程,你只需要继续向上。
你看,这里的事件在层次结构中向上流动,导致视图模型,然后将变量的更新值(因此,状态)向下传递到可组合项。在任何事件向上流动而状态向下流动的系统中,我们称之为 Uni-Directional 数据流,请注意,这是 Compose 的核心。建立这种数据流有一些规则。阅读文档并使用 Compose State Codelab 来了解它,尽管这里的概念已经很清楚了。
谢谢!
旁注:还有其他方法可以初始化 ViewModel。建议使用工厂。检查文档。祝你学习编码好运。
希望你们一切都好。
我是 Kotlin 的新手,所以请耐心等待。
我在jetpack compose中创建了一个页面,如下图所示,它需要装饰但至少我想实现功能。 Simple app interface with lazy column at the left and data display at the right.
左侧是惰性列,显示数组列表中的项目列表。 惰性列中有可点击的卡片。
我需要的是,一旦用户单击列中的项目,其余项目详细信息就会显示在右侧的第二个可组合项中。
代码如下所示
@Composable
fun Greeting() {
Row(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.width(150.dp)
.background(color = Color.LightGray)
) {
items(photoList) { item ->
ProfileCard(photo = item) { <--- i need the item into the other composable.
// photoContent(
// id = item.id,
// owner = item.owner,
// secret = item.secret,
// server = item.server,
// farm = item.farm,
// title = item.title,
// ispublic = item.ispublic,
// isfriend = item.isfriend,
// isfamily = item.isfamily,
// url_s = item.url_s,
// height_s = item.height_s,
// width_s = item.width_s
// )
}
}
}
photoContent(
id = "",
owner = "",
secret = "",
server = "",
farm = 0,
title = "",
ispublic = 0,
isfamily = 0,
isfriend = 0,
url_s = "",
height_s = 0,
width_s = 0
)
}
}
此点击功能来自显示的卡片
@Composable
fun ProfileCard(photo: photo, clickAction: () -> Unit) {
Card( //We changed the default shape of Card to make a cut in the corner.
modifier = Modifier
.padding(top = 4.dp, bottom = 4.dp, start = 16.dp, end = 16.dp)
.fillMaxWidth()
.wrapContentHeight(
align = Alignment.Top
)
.clickable { clickAction.invoke() }, //<-- moving the action to main composable.
elevation = 8.dp,
backgroundColor = Color.White // Uses Surface color by default, so we have to override it.
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
) {
ProfileContent(photo, Alignment.Start)
}
}
}
我尝试了几种方法将数据传递到另一个可组合项中,但我总是收到错误消息:
@composable invocations can only happen from the context of an @composable function
而且我无法删除 @Composable
注释,因为它具有可组合的内容...
如何通过这种方式传递数据?或者没有导航是不可能的?但这需要用户打开另一个页面而不是当前页面...
基本上,您需要为您的选择创建一个状态。 看这个例子:
假设这个 class 是你的 Photo
class...
data class MyUser(
val name: String,
val surname: String
)
然后,像这样定义屏幕:
@Composable
fun MyScreen(users: List<MyUser>) {
// This state will control the selection
var selectedUser by remember {
mutableStateOf<MyUser?>(null)
}
Row(Modifier.fillMaxSize()) {
LazyColumn(
Modifier
.weight(.3f)
.background(Color.Gray)) {
items(users) {
// This would be your ProfileCard
Text(
text = "${it.name} - ${it.surname}",
modifier = Modifier
.clickable {
// when you click on an item,
// the state is updated
selectedUser = it
}
.padding(16.dp)
)
}
}
Column(Modifier.weight(.7f)) {
// if the selection is not null, display it...
// this would be your PhotoContent
selectedUser?.let {
Text(it.name)
Text(it.surname)
}
}
}
}
像这样调用这个函数...
@Composable
fun Greeting() {
// Just a fake user list...
val users = (1..50).map { MyUser("User $it", "Surname $it") }
MyScreen(users = users)
}
会这样
听着,孩子,你需要在这里改变你的思维方式。
好的,让我们深入研究
Jetpack Compose 的核心是状态,状态以变量的形式(主要)保存。如果一个变量在很多地方被用作状态,你必须确保它被很好地维护。不允许 willy-nilly 修改和读取。因此,存储状态的正确方法是在 ViewModel
.
所以通过创建一个像
这样的虚拟机来启动class ProfileViewModel : ViewModel() {
/*This variable shall store the photo,
Which I plan to use as an ID for the Profile Card.*/
var selectedProfile by mutableStateOf(Photo(/*Default Arguments*/)) //Store state as mutableStateOf to ensure proper recompostions
private set //Do not allow external modification, i.e., outside the ViewModel
//We'll be modifying the variable through this method
fun onSelectProfile(photo: Photo) {
selectedProfile = Photo
}
}
这只是一个简单的演示,所以我将视图模型限制为这个。
继续 activity,我们初始化视图模型
imports ...
class MainActivity : ComponentActivity() {
private val profileViewModel by viewModels<ProfileViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Greeting(
selectedProfile = profileViewModel.selectedProfile
onSelectProfile = profileViewModel::onSelectProfile
)
}
很明显,我对您的 Greeting Composable 进行了一些修改。让我们看看它们是什么:-
我基本上添加了两个参数。现在学习这个。在任何需要修改存储在使用它的地方以外的地方的状态,并且有时还需要修改它的情况下,您必须将两个参数传递给可组合项 - 一个用于读取值,一个用于写入值.有道理不是吗。这就是我在这里所做的。
我们将值从存储位置向下传递,将其提供给可组合项。为了允许可组合项触发修改,我们传递了一个 onChange
参数,该参数一直链接到存储位置。在本例中,我们将您的 Greeting Composable 的 onSelectProfile
链接到视图模型中定义的 onSelectProfile
。因此,onSelectProfile
的调用者是 Greeting Composable,但最终在视图模型内部调用了该方法,因为它向上传递。执行视图模型的 onSelectProfile
时, selectedProfile
变量会更新(请参阅视图模型中的方法,它会更新变量)。现在,可组合项正在读取变量,因此,可组合项本身会获取变量的更新值(因为我们使用的是 mutableStateOf
,因此触发了重组)。
因此,看看 Composable
@Composable
fun Greeting(
selectedProfile: Photo,
onSelectProfile: (photo: Photo) -> Unit
) {
Row(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.width(150.dp)
.background(color = Color.LightGray)
) {
items(photoList) { item ->
ProfileCard(photo = item, clickAction = onSelectProfile(item)) //We pass the photo like that
}
}
photoContent(
id = selectedProfile.id,
owner = selectedProfile.,
secret = selectedProfile.<respectiveProperty>,
server = selectedProfile.<respectiveProperty>,
farm = selectedProfile.<respectiveProperty>,
title = selectedProfile.<respectiveProperty>,
ispublic = selectedProfile.<respectiveProperty>,
isfamily = selectedProfile.<respectiveProperty>,
isfriend = selectedProfile.<respectiveProperty>,
url_s = selectedProfile.<respectiveProperty>,
height_s = selectedProfile.<respectiveProperty>,
width_s = selectedProfile.<respectiveProperty>
)
}
}
你看这就是你本质上一直在做的事情。您创建了一个 clickAction()
作为参数,不是吗?你正在经历同样的过程,你只需要继续向上。
你看,这里的事件在层次结构中向上流动,导致视图模型,然后将变量的更新值(因此,状态)向下传递到可组合项。在任何事件向上流动而状态向下流动的系统中,我们称之为 Uni-Directional 数据流,请注意,这是 Compose 的核心。建立这种数据流有一些规则。阅读文档并使用 Compose State Codelab 来了解它,尽管这里的概念已经很清楚了。
谢谢!
旁注:还有其他方法可以初始化 ViewModel。建议使用工厂。检查文档。祝你学习编码好运。