【问题标题】:Jetpack Compose: passing data from lazy column into another composableJetpack Compose:将数据从惰性列传递到另一个可组合
【发布时间】:2021-10-12 01:59:26
【问题描述】:

我希望你们都做得很好。

我是 Kotlin 的新手,所以请耐心等待。

我在 jetpack compose 中创建了一个页面,如下图所示,它需要装饰,但至少我想实现功能。 Simple app interface with lazy column at the left and data display at the right.

左侧是一个惰性列,显示来自 arraylist 的项目列表。 惰性列中有可点击的卡片。

我需要的是,一旦使用从列中单击一个项目,其余项目详细信息将显示在右侧的第二个可组合项中。

代码如下所示

@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 注释,因为它具有可组合的内容...

我怎样才能以这种方式传递数据?或者没有导航就不可能?但这需要用户打开另一个页面而不是当前页面...

【问题讨论】:

    标签: android parameter-passing android-jetpack-compose lazycolumn


    【解决方案1】:

    听着孩子,你需要在这里改变你的思维方式。

    好吧,让我们深入研究

    Jetpack Compose 的核心是状态,并且状态(主要)以变量的形式保存。如果一个变量在很多地方被用作状态,你必须确保它被很好地维护。不允许随意修改和读取。因此,存储状态的正确方法是在 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
        }
    }
    

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

    进入活动,我们初始化视图模型

    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 链接到 viewmodel 中定义的 onSelectProfile。因此,onSelectProfile 的调用者是 Greeting Composable,但该方法最终在视图模型内部调用,因为它传递了。当 viewmodel 的 onSelectProfile 被执行时,selectedProfile 变量被更新(参见 viewmodel 中的方法,它会更新变量)。现在,Composable 正在读取变量,因此,composable 本身会获取变量的更新值(因为我们使用的是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() 作为参数,不是吗?你正在经历同样的过程,你只需要继续向上。

    你看,这里的事件沿着层次结构向上流动,导致视图模型,然后将变量的更新值(以及状态)传递给可组合项。在任何事件向上流动而状态向下流动的系统中,我们称之为单向数据流,请注意,这是 Compose 的核心。建立此数据流有一些规则。阅读文档并参加 Compose State Codelab 来了解它,尽管这里的概念已经很清楚了。

    谢谢!

    旁注:还有其他方法可以初始化 ViewModel。建议使用工厂。检查文档。祝你学习编码好运。

    【讨论】:

    • 非常感谢...我不是以英语为母语的人,但我正在一步一步地进行。我最近因为 COVID 而开始编程,但我很高兴我正在学习新事物......我根据你的工作方式和工作方式进行了工作......
    • 恭喜你有了新的爱好!
    【解决方案2】:

    基本上,您需要为您的选择创建一个状态。 看这个例子:

    假设这个类是你的 Photo 类...

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

    会是这样的

    【讨论】:

    • 非常感谢您的回复...这确实有效,但不适用于我的情况,因为列不是相同的可组合...但这是一个有用的示例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-15
    • 1970-01-01
    • 2021-10-18
    • 2022-12-29
    • 1970-01-01
    相关资源
    最近更新 更多