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)
}
будет так