【开源项目】Compose版SmartRefreshLayout,了解一下~

下拉刷新是我们开发中的常见的需求,官方提供了SwipeRefreshLayout来实现下拉刷新,但我们常常需要定制Header或者Header与内容一起向下滚动,因此SwipeRefreshLayout往往不能满足我们的需求

在使用XML开发时,Github上有不少开源库如 SmartRefreshLayout 实现了下拉刷新功能,可以方便地定制化Header与滚动方式

本文主要介绍如何开发一个简单易用的ComposeSmartRefreshLayout,快速实现下拉刷新功能,如果对您有所帮助可以点个Star: Compose版SmartRefreshLayout


效果图


我们首先看下最终的效果图















基本使用自定义Header














Lottie HeaderFixedBehind(固定在背后)














FixedFront(固定在前面)FixedContent(内容固定)

特性



  1. 接入方便,使用简单,快速实现下拉刷新功能
  2. 支持自定义Header,Header可观察下拉状态并更新UI
  3. 自定义Header支持Lottie,并支持观察下拉状态开始与暂停动画
  4. 支持自定义Translate,FixedBehind,FixedFront,FixedContent等滚动方式
  5. 支持与Paging结合实现上滑加载更多功能

使用


接入


第 1 步:在工程的build.gradle中添加:


allprojects {
repositories {
...
mavenCentral()
}
}

第2步:在应用的build.gradle中添加:


dependencies {
implementation 'io.github.shenzhen2017:compose-refreshlayout:1.0.0'
}

简单使用


SwipeRefreshLayout函数主要包括以下参数:



  1. isRefreshing: 是否正在刷新
  2. onRefresh: 触发刷新回调
  3. modifier: 样式修饰符
  4. swipeStyle: 下拉刷新方式
  5. swipeEnabled: 是否允许下拉刷新
  6. refreshTriggerRate: 刷新生效高度与indicator高度的比例
  7. maxDragRate: 最大刷新距离与indicator高度的比例
  8. indicator: 自定义的indicator,有默认值

在默认情况下,我们只需要传入isRefreshing(是否正在刷新)与onRefresh触发刷新回调两个参数即可


@Composable
fun BasicSample() {
var refreshing by remember { mutableStateOf(false) }
LaunchedEffect(refreshing) {
if (refreshing) {
delay(2000)
refreshing = false
}
}
SwipeRefreshLayout(isRefreshing = refreshing, onRefresh = { refreshing = true }) {
//...
}
}

如上所示:在触发刷新回调时将refreshing设置为true,并在刷新完成后设置为false即可实现简单的下拉刷新功能


自定义Header


SwipeRefreshLayout支持传入自定义的Header,如下所示:


@Composable
fun CustomHeaderSample() {
var refreshing by remember { mutableStateOf(false) }
LaunchedEffect(refreshing) {
if (refreshing) {
delay(2000)
refreshing = false
}
}

SwipeRefreshLayout(
isRefreshing = refreshing,
onRefresh = { refreshing = true },
indicator = {
BallRefreshHeader(state = it)
}) {
//...
}
}

如上所示:BallRefreshHeader即为自定义的Header,Header中会传入SwipeRefreshState,我们通过SwipeRefreshState可获得以下参数



  1. isRefreshing: 是否正在刷新
  2. isSwipeInProgress: 是否正在滚动
  3. maxDrag: 最大下拉距离
  4. refreshTrigger: 刷新触发距离
  5. headerState: 刷新状态,包括PullDownToRefresh,Refreshing,ReleaseToRefresh三个状态
  6. indicatorOffset: Header偏移量

这些参数都是MutableState我们可以观察这些参数的变化以实现Header UI的更新


自定义Lottile Header


Compose目前已支持Lottie,我们接入Lottie依赖后,就可以很方便地实现一个Lottie Header,并且在正在刷新时播放动画,其它时间暂停动画,示例如下:


@Composable
fun LottieHeaderOne(state: SwipeRefreshState) {
var isPlaying by remember {
mutableStateOf(false)
}
val speed by remember {
mutableStateOf(1f)
}
isPlaying = state.isRefreshing
val lottieComposition by rememberLottieComposition(
spec = LottieCompositionSpec.RawRes(R.raw.refresh_one),
)
val lottieAnimationState by animateLottieCompositionAsState(
composition = lottieComposition, // 动画资源句柄
iterations = LottieConstants.IterateForever, // 迭代次数
isPlaying = isPlaying, // 动画播放状态
speed = speed, // 动画速度状态
restartOnPlay = false // 暂停后重新播放是否从头开始
)
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(), contentAlignment = Alignment.Center
) {
LottieAnimation(
lottieComposition,
lottieAnimationState,
modifier = Modifier.size(150.dp)
)

}
}

自定义下滑方式


SwipeRefreshLayout支持以下4种下滑方式


enum class SwipeRefreshStyle {
Translate, //平移,即内容与Header一起向下滑动,Translate为默认样式
FixedBehind, //固定在背后,即内容向下滑动,Header不动
FixedFront, //固定在前面, 即Header固定在前,Header与Content都不滑动
FixedContent //内容固定,Header向下滑动,即官方样式
}

如上所示,其中默认方式为Translate,即内容与Header一起向下滑动

各位可根据需求选择相应的下滑方式,比如要实现类似官方的下滑效果,即可使用FixedContent


上拉加载更多


Compose中,上拉加载更多直接使用Paging3看起来已经足够用了,因此本库没有实现上拉加载更多相关功能

因此如果想要实现上拉加载更多,可自行结合Paging3使用


主要原理


下拉刷新功能,其实主要是嵌套滚动的问题,我们将HeaderContent放到一个父布局中统一管理,然后需要做以下事



  1. 当我们的手指向下滚动时,首先交由Content处理,如果Content滚动到顶部了,再交由父布局处理,然后父布局根据手势进行一定的偏移,增加offset
  2. 当我们松手时,判断偏移的距离,如果大于刷新触发距离则触发刷新,否则回弹到顶部(offset置为0)
  3. 当我们手指向上滚动时,首先交由父布局处理,如果父布局的offset>0则由父布局处理,减少offset,否则则由Content消费手势

NestedScrollConnection介绍


为了实现上面说的需求,我们需要对滚动进行拦截,Compose提供了NestedScrollConnection来实现嵌套滚动


interface NestedScrollConnection {
fun onPreScroll(available: Offset, source: NestedScrollSource): Offset = Offset.Zero

fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset = Offset.Zero

suspend fun onPreFling(available: Velocity): Velocity = Velocity.Zero

suspend fun onPostFling(consumed: Velocity, available: Velocity) = return Velocity.Zero
}

如上所示,NestedScrollConnection主要提供了4个接口



  1. onPreScroll: 先拦截滑动事件,消费后再交给子布局
  2. onPostScroll: 子布局处理完滑动事件后再交给父布局,可获取当前还剩下多少可用的滑动事件偏移量
  3. onPreFling: Fling开始前回调
  4. onPostFling: Fling完成后回调


Fling含义:当我们手指在滑动列表时,如果是快速滑动并抬起,则列表会根据惯性继续飘一段距离后停下,这个行为就是 FlingonPreFling 在你手指刚抬起时便会回调,而 onPostFling 会在飘一段距离停下后回调。



具体实现


上面我们已经介绍了总体思路与NestedScrollConnection API,然后我们应该需要重写以下方法



  1. onPostScroll: 当Content滑动到顶部时,如果继续往上滑,我们就应该增加父布局的offset,因此在onPostScroll中判断available.y > 0,然后进行相应的偏移,对我们来说是个合适的时机
  2. onPreScroll: 当我们上滑时,如果offset>0,则说明父布局有偏移,因此我们应先减小父布局的offset直到0,然后将剩余的偏移量传递给Content,因此下滑时应该使用onPreScroll拦截判断
  3. onPreFling: 当我们松开手时,应判断当前的偏移量是否大于刷新触发距离,如果大于则触发刷新,否则父布局的offset置为0,这个判断在onPreFling时做比较合适

具体实现如下:


internal class SwipeRefreshNestedScrollConnection() : NestedScrollConnection {
override fun onPreScroll(
available: Offset,source: NestedScrollSource
)
: Offset = when {
// 如果用户正在上滑,需要在这里拦截处理
source == NestedScrollSource.Drag && available.y < 0 -> onScroll(available)
else -> Offset.Zero
}

override fun onPostScroll(
consumed: Offset,available: Offset,source: NestedScrollSource
)
: Offset = when {
// 如果用户正在下拉,在这里处理剩余的偏移量
source == NestedScrollSource.Drag && available.y > 0 -> onScroll(available)
else -> Offset.Zero
}

override suspend fun onPreFling(available: Velocity): Velocity {
//如果偏移量大于刷新触发距离,则触发刷新
if (!state.isRefreshing && state.indicatorOffset >= refreshTrigger) {
onRefresh()
}
//不消费速度,直接返回0
return Velocity.Zero
}
}

总结


本文主要介绍如何使用及实现一个Compose版的SmartRefreshLayout,它具有以下特性:



  1. 接入方便,使用简单,快速实现下拉刷新功能
  2. 支持自定义Header,Header可观察下拉状态并更新UI
  3. 自定义Header支持Lottie,并支持观察下拉状态开始与暂停动画
  4. 支持自定义Translate,FixedBehind,FixedFront,FixedContent等滚动方式
  5. 支持与Paging结合实现上滑加载更多功能

项目地址


Compose版SmartRefreshLayout

开源不易,如果项目对你有所帮助,欢迎点赞,Star,收藏~


作者:RicardoMJiang
链接:https://juejin.cn/post/7016306653892968484
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册