Giter VIP home page Giter VIP logo

compose-refreshlayout's Introduction

前言

[DEPRECATED]下拉刷新是我们开发中的常见的需求,官方提供了SwipeRefreshLayout来实现下拉刷新,但我们常常需要定制Header或者Header与内容一起向下滚动,因此SwipeRefreshLayout往往不能满足我们的需求
在使用XML开发时,Github上有不少开源库如SmartRefreshLayout实现了下拉刷新功能,可以方便地定制化Header与滚动方式
本文主要介绍如何开发一个简单易用的ComposeSmartRefreshLayout,快速实现下拉刷新功能,如果对您有所帮助可以点个Star:Compose版SmartRefreshLayout

效果图

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

基本使用 自定义Header
Lottie Header FixedBehind(固定在背后)
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,收藏~

compose-refreshlayout's People

Contributors

ricardojiang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

compose-refreshlayout's Issues

Compose版本更新

大佬,compose更新到1.3.0了,项目什么时候更新一波呀,现在使用新的LocalOverscrollConfiguration会造成下拉刷新卡顿

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.