[英]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.