kotlin 在LazyColumn中对列表项排序- Android Jetpack Compose

fjnneemd  于 2022-11-16  发布在  Kotlin
关注(0)|答案(3)|浏览(168)

尝试实现一个功能,当下拉菜单中的某个项目被选中时(名称、城市、地址),用户可以选择一种方式对啤酒厂列表进行排序。我很难找出我做错了什么。每当我在下拉菜单中选择示例“名称”时,啤酒厂列表就不会排序。我是新手,所以任何建议都会很有帮助。谢谢!
下面是主屏幕的完整代码-

@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun MainScreen(
    navController: NavController,
    mainViewModel: MainViewModel,
    search: String?
) {
    val apiData = brewData(mainViewModel = mainViewModel, search = search)


    Scaffold(
        content = {

            if (apiData.loading == true){
                Column(
                    modifier = Modifier
                        .fillMaxSize()
                        .background(colorResource(id = R.color.grey_blue)),
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    CircularProgressIndicator()
                }

            } else if (apiData.data != null){
                MainContent(bData = apiData.data!!, viewModel = mainViewModel)
            }
        },
        topBar = { BrewTopBar(navController, search) }
    )
}


@Composable
fun BrewTopBar(navController: NavController, search: String?) {
   TopAppBar(
       modifier = Modifier
           .height(55.dp)
           .fillMaxWidth(),
        title = {
            Text(
                stringResource(id = R.string.main_title),
                style = MaterialTheme.typography.h5,
                maxLines = 1
            )
        },
        actions = {
            Row(
                verticalAlignment = Alignment.CenterVertically
            ) {
                Text(text = "$search")
                IconButton(onClick = { navController.navigate(Screens.SearchScreen.name) }) {
                    Icon(
                        modifier = Modifier.padding(10.dp),
                        imageVector = Icons.Filled.Search,
                        contentDescription = stringResource(id = R.string.search)
                    )
                }
            }

        },
       backgroundColor = colorResource(id = R.color.light_purple)
    )
}

@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun MainContent(bData: List<BrewData>, viewModel: MainViewModel){

    val allBreweries = bData.size
    var sortByName by remember { mutableStateOf(false) }
    var sortByCity by remember { mutableStateOf(false) }
    var sortByAddress by remember { mutableStateOf(false) }

    var dataSorted1 = remember { mutableStateOf(bData.sortedBy { it.name }) }
    var dataSorted: MutableState<List<BrewData>>

    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(colorResource(id = R.color.grey_blue))
    ){

        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceAround,
            verticalAlignment = Alignment.CenterVertically
        ){
            // Amount text label
            Text(
                text = "Result(s): ${bData.size}",
                modifier = Modifier.padding(top = 15.dp, start = 15.dp, bottom = 5.dp)
            )
            SortingMenu(sortByName, sortByCity, sortByAddress) // needs mutable booleans for sorting
        }

        // List of Brewery cards
        LazyColumn(
            Modifier
                .fillMaxSize()
                .padding(5.dp)
        ){
            items(allBreweries){ index ->
                Breweries(
                    bData = when {
                        sortByName == true -> remember { mutableStateOf(bData.sortedBy { it.name }) }
                        sortByCity == true ->  remember { mutableStateOf(bData.sortedBy { it.city }) }
                        sortByAddress == true -> remember { mutableStateOf(bData.sortedBy { it.address_2 }) }
                        else -> remember { mutableStateOf(bData) }
                    } as MutableState<List<BrewData>>, // Todo: create a way to select different sorting conditions
                    position = index,
                    viewModel = viewModel
                )
            }
        }
    }
}

@RequiresApi(Build.VERSION_CODES.O)
@Composable
fun Breweries(
    bData: MutableState<List<BrewData>>,
    position: Int,
    viewModel: MainViewModel
){

    val cardNumber = position+1
    val cityApiData = bData.value[position].city
    val phoneNumberApiData = bData.value[position].phone
    val countryApiData = bData.value[position].country
    val breweryTypeApiData = bData.value[position].brewery_type
    val countyApiData = bData.value[position].county_province
    val postalCodeApiData = bData.value[position].postal_code
    val stateApiData = bData.value[position].state
    val streetApiData = bData.value[position].street
    val apiLastUpdated = bData.value[position].updated_at

    val context = LocalContext.current
    val lastUpdated = apiLastUpdated?.let { viewModel.dateTextConverter(it) }
    val websiteUrlApiData = bData.value[position].website_url
    var expanded by remember { mutableStateOf(false) }

    val clickableWebsiteText = buildAnnotatedString {
        if (websiteUrlApiData != null) {
            append(websiteUrlApiData)
        }
    }
    val clickablePhoneNumberText = buildAnnotatedString {
        if (phoneNumberApiData != null){
            append(phoneNumberApiData)
        }
    }

    Column(
        Modifier.padding(10.dp)
    ) {

        //Brewery Card
        Card(
            modifier = Modifier
                .padding(start = 15.dp, end = 15.dp)
                // .fillMaxSize()
                .clickable(
                    enabled = true,
                    onClickLabel = "Expand to view details",
                    onClick = { expanded = !expanded }
                )
                .semantics { contentDescription = "Brewery Card" },
            backgroundColor = colorResource(id = R.color.light_blue),
            contentColor = Color.Black,
            border = BorderStroke(0.5.dp, colorResource(id = R.color.pink)),
            elevation = 15.dp

        ) {

            Column(verticalArrangement = Arrangement.Center) {

                //Number text for position of card
                Text(
                    text = cardNumber.toString(),
                    modifier = Modifier.padding(15.dp),
                    fontSize = 10.sp,
                )

                // Second Row
                BreweryTitle(bData = bData, position = position)

                // Third Row
                // Brewery Details
                CardDetails(
                    cityApiData = cityApiData,
                    stateApiData = stateApiData,
                    streetApiData = streetApiData,
                    countryApiData = countryApiData,
                    countyApiData = countyApiData,
                    postalCodeApiData = postalCodeApiData,
                    breweryTypeApiData = breweryTypeApiData,
                    lastUpdated = lastUpdated,
                    expanded = expanded
                )

                //Fourth Row
                Row(horizontalArrangement = Arrangement.Center,
                    modifier = Modifier.fillMaxWidth()
                ){

                    Column(
                        modifier = Modifier.padding(
                            start = 10.dp, end = 10.dp,
                            top = 15.dp, bottom = 15.dp
                        ),
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {

                        //Phone Number Link
                        LinkBuilder(
                            clickablePhoneNumberText,
                            phoneNumberApiData,
                            modifier = Modifier.padding(bottom = 10.dp)
                        ) {
                            if (phoneNumberApiData != null) {
                                viewModel.callNumber(phoneNumberApiData, context)
                            }
                        }
                        //Website Link
                        LinkBuilder(
                            clickableWebsiteText,
                            websiteUrlApiData,
                            modifier = Modifier.padding(bottom = 15.dp),
                            intentCall = {
                                if (websiteUrlApiData != null) {
                                    viewModel.openWebsite(websiteUrlApiData, context)
                                }
                            }
                        )
                    }
                }
            }
        }
    }
}

@Composable
fun CardDetails(
    cityApiData: String?,
    stateApiData: String?,
    streetApiData: String?,
    countryApiData: String?,
    countyApiData: String?,
    postalCodeApiData: String?,
    breweryTypeApiData: String?,
    lastUpdated: String?,
    expanded: Boolean
){
    // Third Row
    //Brewery Details
    Column(
        modifier = Modifier.padding(
            start = 30.dp, end = 10.dp, top = 25.dp, bottom = 15.dp
        ),
        verticalArrangement = Arrangement.Center
    ) {
        if (expanded) {
            Text(text = "City:  $cityApiData")
            Text(text = "State:  $stateApiData")
            Text(text = "Street:  $streetApiData")
            Text(text = "Country:  $countryApiData")
            Text(text = "County:  $countyApiData")
            Text(text = "Postal Code:  $postalCodeApiData")
            Text(text = "Type:  $breweryTypeApiData")
            Text(text = "Last updated:  $lastUpdated")
        }
    }
}

@Composable
fun BreweryTitle(bData: MutableState<List<BrewData>>, position: Int){
    // Second Row

    Column(
        modifier = Modifier.fillMaxWidth(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Row(
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Center,
        ) {

            // Name of Brewery
            Text(
                text = bData.value[position].name!!,
                modifier = Modifier.padding(start = 15.dp, end = 15.dp),
                fontWeight = FontWeight.Bold,
                maxLines = 3,
                textAlign = TextAlign.Center,
                softWrap = true,
                style = TextStyle(
                    color = colorResource(id = R.color.purple_500),
                    fontStyle = FontStyle.Normal,
                    fontSize = 17.sp,
                    fontFamily = FontFamily.SansSerif,
                    letterSpacing = 2.sp,
                )

            )
        }
    }

}

@Composable
fun LinkBuilder(
    clickableText: AnnotatedString,
    dataText: String?,
    modifier: Modifier,
    intentCall: (String?) -> Unit
){
    if (dataText != null){
        ClickableText(
            text = clickableText,
            modifier = modifier,
            style = TextStyle(
                textDecoration = TextDecoration.Underline,
                letterSpacing = 2.sp
            ),
            onClick = {
                intentCall(dataText)
            }
        )
    }
    else {
        Text(
            text = "Sorry, Not Available",
            color = Color.Gray,
            fontSize = 10.sp
        )
    }
}

//Gets data from view model
@Composable
fun brewData(
    mainViewModel: MainViewModel, search: String?
): DataOrException<List<BrewData>, Boolean, Exception> {

    return produceState<DataOrException<List<BrewData>, Boolean, Exception>>(
        initialValue = DataOrException(loading = true)
    ) {
        value = mainViewModel.getData(search)
    }.value
}

@Composable
fun SortingMenu(sortByName: Boolean, sortByCity: Boolean, sortByAddress: Boolean,) {

    var expanded by remember { mutableStateOf(false) }
    val items = listOf("Name", "City", "Address")
    val disabledValue = "B"
    var selectedIndex by remember { mutableStateOf(0) }

    Box(
        modifier = Modifier
            .wrapContentSize(Alignment.TopStart)
    ) {
        Text(
            text = "Sort by: ${items[selectedIndex]}",
            modifier = Modifier
                .clickable(onClick = { expanded = true })
                .width(120.dp)
        )
        DropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false // todo: sort list data when clicked
                when(selectedIndex){
                    0 -> sortByName == true
                    1 -> sortByCity == true
                    2 -> sortByAddress == true
                } }
        ) {
            items.forEachIndexed { index, text ->
                DropdownMenuItem(onClick = {
                    selectedIndex = index
                    expanded = false
                }) {
                    val disabledText = if (text == disabledValue) {
                        " (Disabled)"
                    } else {
                        ""
                    }
                    Text(text = text + disabledText)
                }
            }
        }
    }
}

//not used
fun sorting(menuList: List<String>, dataList: List<BrewData>, index: Int){
    when{
        menuList[index] == "Name" -> dataList.sortedBy { it.name }
        menuList[index] == "City" -> dataList.sortedBy { it.city }
        menuList[index] == "Address" -> dataList.sortedBy { it.address_2 }
    }
}
vuktfyat

vuktfyat1#

跟踪这么多代码真的很难,尤其是在我的13英寸屏幕上。
您应该将排序逻辑移到ViewModel或useCase,并创建一个排序函数,您可以在用户交互时调用该函数,例如viewmodel.sort(sortType),并使用new更新一个MutableState的值,或者使用mutableStateListOf并使用排序列表更新。
排序是业务逻辑,我会在不包含任何Android相关依赖项的类中进行排序,我使用MVI或MVVM,所以我首选的是用例类,我可以对任何类型、样本列表和预期结果的排序进行单元测试。

mwyxok5s

mwyxok5s2#

你可以用ComparatorModifier.animateItemPlacement()来做,这样看起来会更漂亮!
我将发布一个来自谷歌的例子,这样你就可以理解其中的逻辑:

@Preview
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PopularBooksDemo() {
    MaterialTheme {
        var comparator by remember { mutableStateOf(TitleComparator) }
        Column {
            Row(
                modifier = Modifier.height(IntrinsicSize.Max),
                horizontalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                Text(
                    "Title",
                    Modifier.clickable { comparator = TitleComparator }
                        .weight(5f)
                        .fillMaxHeight()
                        .padding(4.dp)
                        .wrapContentHeight(Alignment.CenterVertically),
                    textAlign = TextAlign.Center
                )
                Text(
                    "Author",
                    Modifier.clickable { comparator = AuthorComparator }
                        .weight(2f)
                        .fillMaxHeight()
                        .padding(4.dp)
                        .wrapContentHeight(Alignment.CenterVertically),
                    textAlign = TextAlign.Center
                )
                Text(
                    "Year",
                    Modifier.clickable { comparator = YearComparator }
                        .width(50.dp)
                        .fillMaxHeight()
                        .padding(4.dp)
                        .wrapContentHeight(Alignment.CenterVertically),
                    textAlign = TextAlign.Center
                )
                Text(
                    "Sales (M)",
                    Modifier.clickable { comparator = SalesComparator }
                        .width(65.dp)
                        .fillMaxHeight()
                        .padding(4.dp)
                        .wrapContentHeight(Alignment.CenterVertically),
                    textAlign = TextAlign.Center
                )
            }
            Divider(color = Color.LightGray, thickness = Dp.Hairline)
            LazyColumn(
                Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.spacedBy(16.dp)
            ) {
                val sortedList = PopularBooksList.sortedWith(comparator)
                items(sortedList, key = { it.title }) {
                    Row(
                        Modifier.animateItemPlacement()
                            .height(IntrinsicSize.Max),
                        horizontalArrangement = Arrangement.spacedBy(8.dp)
                    ) {
                        Text(
                            it.title,
                            Modifier.weight(5f)
                                .fillMaxHeight()
                                .padding(4.dp)
                                .wrapContentHeight(Alignment.CenterVertically),
                            textAlign = TextAlign.Center
                        )
                        Text(
                            it.author,
                            Modifier.weight(2f)
                                .fillMaxHeight()
                                .padding(4.dp)
                                .wrapContentHeight(Alignment.CenterVertically),
                            textAlign = TextAlign.Center
                        )
                        Text(
                            "${it.published}",
                            Modifier.width(55.dp)
                                .fillMaxHeight()
                                .padding(4.dp)
                                .wrapContentHeight(Alignment.CenterVertically),
                            textAlign = TextAlign.Center
                        )
                        Text(
                            "${it.salesInMillions}",
                            Modifier.width(65.dp)
                                .fillMaxHeight()
                                .padding(4.dp)
                                .wrapContentHeight(Alignment.CenterVertically),
                            textAlign = TextAlign.Center
                        )
                    }
                }
            }
        }
    }
}

private val TitleComparator = Comparator<Book> { left, right ->
    left.title.compareTo(right.title)
}

private val AuthorComparator = Comparator<Book> { left, right ->
    left.author.compareTo(right.author)
}

private val YearComparator = Comparator<Book> { left, right ->
    right.published.compareTo(left.published)
}

private val SalesComparator = Comparator<Book> { left, right ->
    right.salesInMillions.compareTo(left.salesInMillions)
}

private val PopularBooksList = listOf(
    Book("The Hobbit", "J. R. R. Tolkien", 1937, 140),
    Book("Harry Potter and the Philosopher's Stone", "J. K. Rowling", 1997, 120),
    Book("Dream of the Red Chamber", "Cao Xueqin", 1800, 100),
    Book("And Then There Were None", "Agatha Christie", 1939, 100),
    Book("The Little Prince", "Antoine de Saint-Exupéry", 1943, 100),
    Book("The Lion, the Witch and the Wardrobe", "C. S. Lewis", 1950, 85),
    Book("The Adventures of Pinocchio", "Carlo Collodi", 1881, 80),
    Book("The Da Vinci Code", "Dan Brown", 2003, 80),
    Book("Harry Potter and the Chamber of Secrets", "J. K. Rowling", 1998, 77),
    Book("The Alchemist", "Paulo Coelho", 1988, 65),
    Book("Harry Potter and the Prisoner of Azkaban", "J. K. Rowling", 1999, 65),
    Book("Harry Potter and the Goblet of Fire", "J. K. Rowling", 2000, 65),
    Book("Harry Potter and the Order of the Phoenix", "J. K. Rowling", 2003, 65)
)

private class Book(
    val title: String,
    val author: String,
    val published: Int,
    val salesInMillions: Int
)
bkhjykvo

bkhjykvo3#

您有大量代码,而我只能看到lists,因此很难猜出发生了什么,但基于
啤酒厂列表不排序
我会建议创建一个非常简单的工作应用程序与1 data class 1 Viewmodel 1 Screen与一个LazyColumn和1 Button
请考虑以下内容:
您的data class

data class Person(
   val id: Int,
   val name: String
)

您的ViewModel

class MyViewModel {
    val peopleList = mutableStateListOf<Person>() // SnapshotStateList
    
    fun onSortButtonClicked() {
         // do your sorting logic here 
         // update your mutable`State`List
    }
}

您的Composable屏幕

@Composable // I just put the view model as an argument, don't follow it  you don't need to
fun MyScreen(viewModel: MyViewModel) {
      val myList = viewModel.peopleList

       LazyColumn {
           items(items = myList, key = { person -> person.id }) { person ->
              //.. your item composable //
       }

       Button(
           onClick = { viewModel.onSortButtonClicked() }
       )
   }
}

它可以工作,但不建议在StatemutableState中使用MutableList,因为对其元素所做的任何更改都不会触发re-composition,最好的方法是对整个List执行manually copy并创建一个新的,这也是不建议的。这就是为什么SnapshotStateList<T>mutableStateListOf<T>not only recommended,但在Jetpack Compose开发中有效处理lists的唯一方法。
使用SnapshotStateList,任何更新(根据我的经验)例如deleting一个元素,modifying通过执行.copy(..)并重新插入到相同的索引将保证读取该元素的Composable上的re-composition,在“工作示例代码”的情况下,如果我把SnapshotStateList中的一个人的名字改成peopleList[index] = person.copy(name="Mr Person"),那么读取那个人对象的itemcomposable将变成re-compose.至于你的Sorting问题,我还没有试过,所以我不确定,但我认为只要在SnapshotStateList中执行一个排序操作,列表就会排序。
尽管我对使用SnapshotStateList作为“工作示例代码”很有信心,但我还是对我所说的话持保留态度,因为在它的背后还有很多细微的差别和事情,我很小心,不想随便乱讲,但正如你所说的
我是新手
我也是。我相信这是一个很好的起点处理collections/list在喷气背包组成。

相关问题