Jetpack Compose:将数据从惰性列传递到另一个组合函数

3

希望大家都过得很好。

我对Kotlin非常不熟悉,请耐心等待。

我创建了jetpack compose中的一个单页面,如下图所示,它需要装饰,但至少我想实现功能。 在左侧是一个带有卡片的懒惰列,可显示来自数组列表的项目列表。卡片可点击。

我需要的是,一旦用户从列中点击某个项目,则在右侧第二个可组合项中显示其余项目细节。

代码看起来像这样

@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 只能在 @composable 函数的上下文中调用

而我无法删除 @Composable 注释,因为它包含可组合内容...

如何以这种方式传递数据?或者没有导航是不可能的吗?但这需要用户打开另一页而不是当前页...

2个回答

1
基本上,您需要为所选内容创建一个状态。请参考以下示例:
假设此类是您的照片类...
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)
}

会是这样子的。

enter image description here


非常感谢您的回复...这确实有效,但在我的情况下不适用,因为列不是可组合的...但这是一个有用的例子。 - Saher Al-Sous

1

孩子,你需要改变你的思维方式。

好的,让我们深入探讨一下。

Jetpack Compose 的核心是状态,状态以变量的形式保存(主要是)。如果一个变量在多个地方用作状态,您必须确保它被良好维护。不要允许随意修改和读取。因此,存储状态的正确方法是在 ViewModel 中。

因此,首先创建一个 VM,例如:

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
    }
}

这只是一个简单的演示,因此我将限制视图模型。

接下来,我们初始化视图模型。

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
        )

}

很明显,我对您的问候组合函数进行了一些修改。让我们看看它们是什么:

我基本上添加了两个参数。现在要学习的是,在任何需要修改存储在其他位置而不是使用位置的状态,并且有时还需要修改它的情况下,必须向可组合项传递两个参数 - 一个用于读取值,另一个用于写入值。很有道理,不是吗。这就是我在这里所做的。

我们将值从存储它的位置向下传递,提供给组合函数。为了允许组合函数触发修改,我们传递一个`onChange`参数,它与存储位置一直链接在一起。在本例中,我们将你的问候组合函数的`onSelectProfile`与视图模型中定义的`onSelectProfile`链接起来。因此,调用`onSelectProfile`的是Greeting Composable,但最终方法在viewmodel内调用。当执行viewmodel的`onSelectProfile`时,`selectedProfile`变量会得到更新(请参见viewmodel中的方法,它更新变量)。现在,Composable正在读取该变量,因此,组合函数本身获得了变量的更新值(因为我们使用了`mutableStateOf`,因此会触发重新组合)。
因此,看看这个组合函数。
@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()作为参数,对吧?你正在进行同样的过程,只需要继续向上。

你看,在这里事件沿着层次结构向上流动,最终到达视图模型,然后将更新后的变量值(因此也包括状态)传递给可组合项。在任何事件向上流动,状态向下流动的系统中,我们称之为单向数据流,注意,这是Compose的核心。建立这种数据流有一些规则。阅读文档并参加Compose状态Codelab以了解详情,虽然这个概念已经在这里讲得很清楚了。

谢谢!

附注:还有其他初始化ViewModel的方式。使用工厂是推荐的。请查看文档。祝你学习编程顺利。


非常感谢您...我不是以英语为母语的人,但我正在逐步前进。因为COVID,我最近开始涉足编程,但我很兴奋能学到新东西...我根据您的方法进行了工作,它起作用了... - Saher Al-Sous
恭喜你有了新的爱好! - Richard Onslow Roper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接