关于如何在加载项目时实现滑动刷新功能以及占位符的简短指南
今天,许多应用程序都有需要在某个时候刷新的数据。 您可以在一段时间后刷新数据或使用套接字来始终拥有最新的数据,但是如果您想要允许用户开始刷新数据的功能怎么办?
这可以通过一个按钮来完成,但在某些情况下,更好的用户体验将是滑动刷新。 今天,我们将使用 Accompanist 库来实现它。
滑动刷新
首先,让我们添加一个依赖项:
implementation "com.google.accompanist:accompanist-swiperefresh:0.25.1"
注意:检查是否有此依赖项的更新版本。
接下来是创建一个简单的ViewModel,它将保存我们的数据和刷新逻辑。 在这里,项目将包含随机图像和数字。 这是它的样子:
class MainViewModel : ViewModel() {
private val _isRefreshing = MutableStateFlow(false)
val isRefreshing = _isRefreshing.asStateFlow()
private val _currentTime = MutableStateFlow(Instant.now())
val currentTime = _currentTime.asStateFlow()
private val _items = MutableStateFlow(generateItems())
val items = _items.asStateFlow()
fun refresh() = viewModelScope.launch {
_isRefreshing.update { true }
// Simulate API call
delay(2000)
_currentTime.value = Instant.now()
_items.value = generateItems()
_isRefreshing.update { false }
}
private fun generateItems(): List<RowItem> {
val list = mutableListOf<RowItem>()
for (i in 1 until 20) {
list.add(
RowItem(
rowImage = randomImage(),
number = Random.nextInt(1, 1000)
)
)
}
return list
}
private fun randomImage(
seed: Int = (0..100000).random(),
width: Int = 300,
height: Int = width,
): String {
return "https://picsum.photos/seed/$seed/$width/$height"
}
}
data class RowItem(
val rowImage: String = "",
val number: Int = -1
)
isRefreshing 是一个布尔值,我们将在 swipeRefreshState 中使用它,我们将在后面解释。 items 只是包含随机图像和数字的 20 个项目的列表。
现在,让我们创建我们的屏幕:
@Composable
fun MainScreen(
viewModel: MainViewModel = viewModel()
) {
val isRefreshing = viewModel.isRefreshing.collectAsState().value
val currentTime = viewModel.currentTime.collectAsState().value
val items = viewModel.items.collectAsState().value
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isRefreshing)
SwipeRefresh(
state = swipeRefreshState,
onRefresh = viewModel::refresh,
modifier = Modifier
.fillMaxSize()
.padding(
vertical = 32.dp,
horizontal = 16.dp
)
) {
Column {
Text(
text = "Welcome to Swipe-to-Refresh!",
style = MaterialTheme.typography.h5,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(32.dp))
Text(
text = currentTime.toString(),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.End
)
Spacer(modifier = Modifier.height(8.dp))
LazyColumn {
items(items) {
Item(
rowItem = it
)
}
}
}
}
}
@Composable
fun Item(
rowItem: RowItem
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
elevation = 4.dp
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = rememberAsyncImagePainter(rowItem.rowImage),
contentDescription = rowItem.number.toString(),
modifier = Modifier.size(64.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Text(text = "Number: ${rowItem.number}")
}
}
}
我们正在收集我们的状态并使用 isRefreshing 的值创建 swipeRefreshState。我们将这个状态传递给 SwipeRefresh,但如果需要,我们也可以访问它的属性 isRefreshing 和 isSwipeInProgress。在 SwipeRefresh 中,我们有一个标题、当前时间和项目列表。每行项目只显示一个图像和数字。
SwipeRefresh 具有三个强制参数:
- state: SwipeRefreshState — 可以提升以控制和观察 SwipeRefresh 更改的状态对象
- onRefresh: () -> Unit — 完成滑动刷新手势时调用的 Lambda
- content: @Composable () -> Unit — 包含可组合滚动的内容
一些有趣的可选参数是:
- swipeEnabled: Boolean - 布局是否应该对滑动手势做出反应
- refreshTriggerDistance: Dp — 触发刷新的最小滑动距离
- indicatorAlignment: Alignment — 指标的对齐方式。默认为 Alignment.TopCenter
- indicatorPadding: PaddingValues — 指标的内容填充,如果需要可以插入指标
- indicator:@Composable (state: SwipeRefreshState, refreshTrigger: Dp) — 表示当前状态的指标。默认情况下,这将使用 SwipeRefreshIndicator
对于指标参数,您可以创建自己的可组合项,但该库为我们提供了 SwipeRefreshIndicator,这是我们可以使用的非常好的可组合项。
它需要两个参数:
- state: SwipeRefreshState — 传递到 SwipeRefresh 指示器块的 SwipeRefreshState
- refreshTriggerDistance: Dp — 触发刷新的最小滑动距离
一些可选参数是:
- fade: Boolean — 箭头在滚动时是否应该淡入/淡出,默认为 true
- scale: Boolean — 指示器在滚动时是否应按比例放大/缩小,默认为 false
- arrowEnabled: Boolean — 是否应在指标上绘制箭头,默认为 true
- backgroundColor: Color — 指示器背景表面的颜色
还有更多参数,但不需要全部遍历。如果您想了解更多信息,请务必在官方文档中查看。
这就是 SwipeRefresh 的全部内容,现在让我们实现占位符,这是来自 Accompanist 的另一个不错的库。
占位符
通常,项目的加载由某种加载微调器显示。另一种显示项目正在加载的方法是使用占位符。
Accompanist 创建了一个库,为我们提供了用于显示占位符的修饰符。实际上有两个占位符库。一个是基础,另一个是材料。建议我们使用 Material,但可以随意使用您需要的任何东西。没有太大区别,API 大多是等价的。在本博客中,我们使用的是 Material。所以,让我们用这个命令导入它:
implementation "com.google.accompanist:accompanist-placeholder-material:0.25.1"
注意:检查是否有此依赖项的更新版本。
在继续 MainScreen 之前,让我们快速编辑 MainViewModel。 添加 init 和 isLoading StateFlow。 此外,使用 20 个默认 RowItem 初始化项目。
private val _items = MutableStateFlow(List(size = 20) { RowItem() })
val items = _items.asStateFlow()
private val _isLoading = MutableStateFlow(true)
val isLoading = _isLoading.asStateFlow()
init {
viewModelScope.launch {
delay(2000)
_items.value = generateItems()
_isLoading.value = false
}
}
我们的 ViewModel 现在看起来像这样:
class MainViewModel : ViewModel() {
private val _isRefreshing = MutableStateFlow(false)
val isRefreshing = _isRefreshing.asStateFlow()
private val _currentTime = MutableStateFlow(Instant.now())
val currentTime = _currentTime.asStateFlow()
private val _items = MutableStateFlow(List(size = 20) { RowItem() })
val items = _items.asStateFlow()
private val _isLoading = MutableStateFlow(true)
val isLoading = _isLoading.asStateFlow()
init {
viewModelScope.launch {
delay(2000)
_items.value = generateItems()
_isLoading.value = false
}
}
fun refresh() = viewModelScope.launch {
_isRefreshing.update { true }
// Simulate API call
delay(2000)
_currentTime.value = Instant.now()
_items.value = generateItems()
_isRefreshing.update { false }
}
private fun generateItems(): List<RowItem> {
val list = mutableListOf<RowItem>()
for (i in 1 until 20) {
list.add(
RowItem(
rowImage = randomSampleImageUrl(),
number = Random.nextInt(1, 1000)
)
)
}
return list
}
private fun randomSampleImageUrl(
seed: Int = (0..100000).random(),
width: Int = 300,
height: Int = width,
): String {
return "https://picsum.photos/seed/$seed/$width/$height"
}
}
data class RowItem(
val rowImage: String = "",
val number: Int = -1
)
接下来是在我们的屏幕中收集 isLoading,然后将其用作我们的占位符。 我们正在向 Item 可组合项添加一个新参数 childModifier:Modifier。
Item(
rowItem = it,
childModifier = Modifier.placeholder(
visible = isLoading,
highlight = PlaceholderHighlight.fade(),
)
)
如您所见,该库为占位符提供了一个修饰符。必需的参数是可见的:布尔值,它确定是否应显示占位符或内容。如果 visible 为真,那么将有一个占位符来填充应用它的可组合项的大小,而不是内容。
可选参数有:
- color: Color — 用于绘制占位符 UI 的颜色。如果提供了 Color.Unspecified,则占位符将使用 PlaceholderDefaults.color
- shape: Shape — 占位符的所需形状。如果提供 null,占位符将使用 MaterialTheme.shapes 中设置的小形状
- highlight: PlaceholderHighlight — 可选的高亮动画。有两个预先创建的占位符动画,淡入淡出和微光
- placeholderFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> — 将占位符淡入/淡出屏幕时使用的转换规范。为过渡定义的布尔参数可见
- contentFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> — 将内容淡入/淡出屏幕时使用的转换规范。为过渡定义的布尔参数可见
我们的 MainScreen 现在看起来像这样:
@Composable
fun MainScreen(
viewModel: MainViewModel = viewModel()
) {
val isRefreshing = viewModel.isRefreshing.collectAsState().value
val isLoading = viewModel.isLoading.collectAsState().value
val currentTime = viewModel.currentTime.collectAsState().value
val items = viewModel.items.collectAsState().value
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isRefreshing)
SwipeRefresh(
state = swipeRefreshState,
onRefresh = viewModel::refresh,
modifier = Modifier
.fillMaxSize()
.padding(
vertical = 32.dp,
horizontal = 16.dp
)
) {
LazyColumn {
item {
Text(
text = "Welcome to Swipe-to-Refresh!",
style = MaterialTheme.typography.h5,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(32.dp))
Text(
text = currentTime.toString(),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.End
)
Spacer(modifier = Modifier.height(8.dp))
}
items(items) {
Item(
rowItem = it,
childModifier = Modifier.placeholder(
visible = isLoading,
highlight = PlaceholderHighlight.fade(),
)
)
}
}
}
}
@Composable
fun Item(
rowItem: RowItem,
childModifier: Modifier = Modifier,
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
elevation = 4.dp
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = rememberAsyncImagePainter(rowItem.rowImage),
contentDescription = rowItem.number.toString(),
modifier = childModifier.size(64.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = "Number: ${rowItem.number}",
modifier = childModifier.fillMaxWidth()
)
}
}
}
就这样。 我希望你喜欢它。
关注七爪网,获取更多APP/小程序/网站源码资源!
,