Jetpack Compose: передача данных из ленивого столбца в другой составной компонент

Я надеюсь, что у всех у вас все хорошо.

Я новичок в Котлине, поэтому проявите ко мне терпение.

Я создал одну страницу в Jetpack Compose, как вы можете видеть на изображении ниже, для нее нужны украшения, но, по крайней мере, я хочу реализовать эту функциональность. Простой интерфейс приложения с ленивым столбцом слева и отображением данных справа.

слева - ленивый столбец, в котором отображается список элементов из arrayylist. в ленивом столбце есть карточки, на которые можно щелкнуть.

Мне нужно, чтобы после щелчка по элементу в столбце остальная часть сведений об элементе отображалась во втором компонуемом элементе справа.

код выглядит так

      @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 ответа

Слушай, малыш, тебе нужно изменить свой образ мышления здесь.

Хорошо, давай копаемся

В основе 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
        )

}

Как ясно, я сделал несколько модификаций для вашего составного приветствия. Посмотрим, какие они: -

По сути, я добавил два параметра. Теперь узнай это. В любом сценарии, когда вам нужно изменить состояние, хранящееся где-то в другом месте, кроме того, где оно используется, и вам также необходимо время от времени его изменять, вы должны передать два параметра в составной объект - один для чтения значения и один для записи значения . Имеет смысл, не так ли. Вот что я здесь сделал.

Мы передаем значение вниз из места, где оно хранится, и передаем его компоненту. И чтобы компонент мог запускать модификации, мы передаем onChangeпараметр, который полностью привязан к месту хранения. В этом случае мы привязываем ваши Greeting Composable к определенным в viewmodel. Следовательно, вызывающий объект - это 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()в качестве параметра не так ли? Вы проходили тот же процесс, вам просто нужно было продолжать движение вверх.

Видите ли, здесь события идут вверх по иерархии, ведущей к модели представления, которая затем передает обновленное значение переменной (и, следовательно, состояние) в составные компоненты. В любой системе, где события идут вверх, а состояние течет вниз, мы называем это однонаправленным потоком данных, и имейте в виду, что это лежит в основе Compose. Есть несколько правил для создания этого потока данных. Прочтите Документы и возьмите Compose State Codelab, чтобы узнать об этом, хотя концепция здесь довольно ясна.

Спасибо!

Боковое примечание: есть другие способы инициализировать ViewModel. Рекомендуется использовать завод. Проверьте документацию для этого. Удачи в изучении программирования.

По сути, вам нужно создать состояние для вашего выбора. См. Этот пример:

Допустим, этот класс ваш 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)
}

будет так

Другие вопросы по тегам