简体   繁体   English

Android 分页撰写:当任何查询更改时如何滚动到顶部?

[英]Android Paging Compose: How to scroll to top when any of the queries changed?

I have a list of products.我有一个产品清单。 On the ProductsScreen I have some filter options, search box, also filter chips:ProductsScreen我有一些过滤选项,搜索框,还有过滤芯片:

I'm using paging 3 library for the pagination.我正在使用paging 3 库进行分页。 I want to scroll to the top when the search query or selected filter chip has changed.当搜索查询或选定的过滤器芯片发生变化时,我想滚动到顶部。 Here is my code:这是我的代码:

ProductScreen

@Composable
fun ProductsScreen(
    modifier: Modifier = Modifier,
    navController: NavController,
    viewModel: ProductsScreenViewModel = hiltViewModel(),
    onProductItemClick: (String) -> Unit = {},
    onProductFilterButtonClick: (ProductsFilterPreferences) -> Unit = {},
) {
    //products
    val products = viewModel.products.collectAsLazyPagingItems()
    
    //load state
    val isLoading = products.loadState.refresh is LoadState.Loading

    val scrollToTop = viewModel.scrollToTop.observeAsState()

    val scaffoldState = rememberScaffoldState()

    val listState = rememberLazyListState()

    val scope = rememberCoroutineScope()

    val query by viewModel.query.collectAsState()

    val context = LocalContext.current

    //not working
    LaunchedEffect(key1 = Unit) {
        scrollToTop.value?.getContentIfNotHandled()?.let { scroll ->
            if (scroll) {
                listState.scrollToItem(0)
            }
        }
    }

RubiBrandsScaffold(
        modifier = modifier,
        scaffoldState = scaffoldState,
        topBar = {
            ProductsScreenToolbar(
                showFilterBadge = viewModel.showFilterBadge,
                onFilterButtonClick = { onProductFilterButtonClick(viewModel.productsFilterPreferences.value) },
                searchQuery = query,
                onTrailingIconClick = {
                    viewModel.setQuery("")
                },
                onQueryChange = { query ->
                    viewModel.setQuery(query)
                },
                onSearchButtonClick = { query ->
                    viewModel.onSearch(query)
                },
                onChipClick = { chip ->
                    viewModel.onChipSelected(chip)
                },
                selectedChip = viewModel.selectedChip,
            )
        },
        floatingActionButton = {
            AnimatedVisibility(
                visible = showFab,
                enter = scaleIn(),
                exit = scaleOut()
            ) {
                FloatingActionButton(
                    backgroundColor = MaterialTheme.colors.primary,
                    onClick = {
                        scope.launch {
                            //working
                            listState.scrollToItem(0)
                        }
                    },
                    modifier = Modifier
                        .padding(
                            dimensionResource(id = R.dimen.dimen_16)
                        )
                ) {
                    Icon(
                        painter = painterResource(id = R.drawable.ic_up),
                        contentDescription = null,
                        tint = MaterialTheme.colors.onPrimary
                    )
                }
            }
        }
    ) { paddingValues ->
        SwipeRefresh(
            indicator = { state, trigger ->
                RubiBrandsSwipeRefreshIndicator(state = state, trigger = trigger)
            }, state = swipeRefreshState, onRefresh = products::refresh
        ) {
            LazyColumn(
                state = listState,
                modifier = Modifier.fillMaxSize()
            ) {
                items(products) { product ->
                    product?.let {
                        ProductItem(
                            modifier = Modifier.clickable {
                                if (!isLoading) onProductItemClick(
                                    product.id
                                )
                            },
                            childModifier = Modifier.shimmerModifier(isLoading),
                            productImage = product.productImage?.get(0),
                            productTitle = product.productTitle,
                            productCount = product.productCount,
                            productStatus = product.productStatus?.asString(context)
                        )
                    }
                }
                products.apply {
                    when {
                        loadState.source.refresh is LoadState.Loading -> {
                            items(10) {
                                ProductItem(
                                    childModifier = Modifier.shimmerModifier(true),
                                    productImage = null,
                                    productTitle = "",
                                    productCount = "",
                                    productStatus = ""
                                )
                            }
                        }

                        loadState.source.append is LoadState.Loading -> {
                            item {
                                CircularProgressIndicator(
                                    modifier = Modifier
                                        .fillMaxWidth()
                                        .padding(16.dp)
                                        .wrapContentWidth(Alignment.CenterHorizontally)
                                )
                            }
                        }

                        loadState.source.refresh is LoadState.Error -> {
                            val e = products.loadState.refresh as LoadState.Error
                            item {
                                ErrorItem(
                                    modifier = Modifier.fillParentMaxSize(),
                                    message = e.error.localizedMessage
                                        ?: stringResource(id = R.string.something_went_wrong),
                                    onRetryClick = products::retry
                                )
                            }
                        }

                        loadState.source.append is LoadState.Error -> {
                            val e = products.loadState.append as LoadState.Error
                            item {
                                ErrorItem(
                                    modifier = Modifier
                                        .fillParentMaxWidth()
                                        .wrapContentHeight(),
                                    message = e.error.localizedMessage
                                        ?: stringResource(id = R.string.something_went_wrong),
                                    onRetryClick = products::retry
                                )
                            }
                        }

                        loadState.source.refresh is LoadState.NotLoading && loadState.source.refresh !is LoadState.Error && products.itemCount < 1 -> {
                            item {
                                RubiBrandsEmptyListView(modifier = Modifier.fillParentMaxSize())
                            }
                        }
                    }
                }
            }
        }
    }

}

Here is also my ProductsScreenViewModel :这也是我的ProductsScreenViewModel


@HiltViewModel
class ProductsScreenViewModel @Inject constructor(
    private val productsScreenUseCase: ProductsScreenUseCase
) : ViewModel() {


    var showFilterBadge by mutableStateOf(false)
        private set

    private val _query = MutableStateFlow<String>("")

    val query: StateFlow<String> = _query

    var selectedChip by mutableStateOf<ProductsChips>(ProductsChips.ALL)

    private val _productsFilterPreferences = MutableStateFlow(ProductsFilterPreferences())

    val productsFilterPreferences: StateFlow<ProductsFilterPreferences> = _productsFilterPreferences

    private val _scrollToTop = MutableLiveData<Event<Boolean>>()

    val scrollToTop: LiveData<Event<Boolean>> get() = _scrollToTop

    val products =
        _productsFilterPreferences.flatMapLatest { filterPreferences ->
            showFilterBadge = filterPreferences.sort.value != null
                    || filterPreferences.saleStatus.value != null
                    || filterPreferences.stockStatus.value != null
                    || filterPreferences.sku != null
                    || filterPreferences.priceOptions.value != R.string.all

            productsScreenUseCase.fetchProducts(params = filterPreferences)
        }.cachedIn(viewModelScope)


    fun setFilters(
        filters: ProductsFilterPreferences
    ) {
        _scrollToTop.value = Event(true)
        _productsFilterPreferences.update {
            it.copy(
                sort = filters.sort,
                state = filters.state,
                saleStatus = filters.saleStatus,
                stockStatus = filters.stockStatus,
                sku = filters.sku,
                priceOptions = filters.priceOptions
            )
        }
    }

    fun setQuery(query: String) {
        if (query.isEmpty()) {
            onSearch(query)
        }
        _query.value = query
    }

    fun onSearch(query: String) {
        //if I press the search button I'm setting scroll to top to true
        _scrollToTop.value = Event(true)
        _productsFilterPreferences.update {
            it.copy(query = query.trim().ifEmpty { null })
        }
    }

    fun onChipSelected(chip: ProductsChips) {
        selectedChip = chip
        //if I change the chip I'm setting scroll to top to true
        _scrollToTop.value = Event(true)
        _productsFilterPreferences.update {
            it.copy(state = chip.value)
        }
    }
}


There is a related question about that but it is valid for XML.有一个相关的问题,但它对 XML 有效。

So whenever the search query or selected chip has changed I'm setting the scrollToTop value to true and then observing it from my ProductsScreen composable.因此,每当搜索查询或所选芯片发生更改时,我都会将scrollToTop值设置为 true,然后从我的ProductsScreen可组合中观察它。 But it is not working.但它不起作用。

You can try using the LaunchedEffect and the scrollState of Lazycolumn to scroll it to top.您可以尝试使用 LaunchedEffect 和 Lazycolumn 的 scrollState 将其滚动到顶部。

val scrollState = rememberLazyListState()

LazyColumn( state = scrollState){}

LaunchedEffect(true) {
        scrollState.animateScrollToItem(0)
    }   

I've just put the LaunchEffect within the if block and the issue have been resolved.我刚刚将LaunchEffect放在 if 块中,问题已经解决。 Here is the code:这是代码:

    scrollToTop?.getContentIfNotHandled()?.let { scroll ->
        if (scroll) {
            LaunchedEffect(key1 = Unit) {
                listState.scrollToItem(0)
            }
        }
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Android Paging 3 jetpack 撰写如何在导航返回时滚动到顶部或重置分页 - Android Paging 3 jetpack compose how to scroll to top or reset the paging when navigating back Android Jetpack Compose 滚动到顶部 - Android Jetpack Compose Scroll to Top Android Jetpack Compose BasicTextField 在获得焦点时滚动到顶部,如何停止它 - Android Jetpack Compose BasicTextField scroll to top when get focus, how to stop it Android 使用 Jetpack Compose 无滚动地无限加载 Paging 3 库 - Android Paging 3 library loading infinitely without scroll with Jetpack Compose Android Jetpack Compose中如何结合Paging使用LazyColumn stickyHeader? - How to use LazyColumn stickyHeader in combination with Paging in Android Jetpack Compose? Compose - 滚动外部 HorizontalPager 时将 VerticalPager 滚动到顶部 - Compose - Scroll VerticalPager to the top when outer HorizontalPager is scrolled Android ScrollView如何在滚动到屏幕顶部时将组件保留在屏幕上? - Android ScrollView how to keep component on screen when it scroll to top of the screen? Android:当ListView中没有/限制项目时,如何将标题滚动到顶部? - Android: How to scroll header to top when there is no/limited items in ListView? android,当在viewpager中滚动recyclerview时,如何使父scrollview滚动到顶部 - android when recyclerview in a viewpager is scrolled, how to top parent scrollview scroll 如何在Android中将WebView滚动到顶部? - How to scroll WebView to top in android?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM