最完整的SmartRefreshLayout与Jetpack Compose集成指南:从XML迁移到声明式UI

最完整的SmartRefreshLayout与Jetpack Compose集成指南:从XML迁移到声明式UI

【免费下载链接】SmartRefreshLayout 🔥下拉刷新、上拉加载、二级刷新、淘宝二楼、RefreshLayout、OverScroll,Android智能下拉刷新框架,支持越界回弹、越界拖动,具有极强的扩展性,集成了几十种炫酷的Header和 Footer。 【免费下载链接】SmartRefreshLayout 项目地址: https://gitcode.com/gh_mirrors/smar/SmartRefreshLayout

你是否正在将Android项目从传统XML布局迁移到Jetpack Compose(声明式UI)?是否苦于找不到成熟的下拉刷新解决方案?本文将带你从零开始实现SmartRefreshLayout与Jetpack Compose的无缝集成,解决嵌套滚动冲突、状态管理、主题适配等核心痛点,提供3种集成方案和5个实战案例,让你的Compose项目轻松拥有媲美原生的刷新体验。

读完本文你将获得:

  • 3种可行的SmartRefreshLayout与Compose集成方案及选型建议
  • 完整的自定义Compose包装组件实现代码
  • 5个常见场景的实战案例(列表刷新、分页加载、二级刷新等)
  • 解决90%集成问题的避坑指南和性能优化技巧

为什么需要SmartRefreshLayout+Compose组合

Jetpack Compose作为Google推出的现代UI工具包,采用声明式编程模型,极大简化了UI开发流程。但作为较新的技术,其生态系统仍在完善中,特别是在下拉刷新领域,官方提供的SwipeRefresh组件功能单一,无法满足复杂业务需求。

SmartRefreshLayout作为Android平台最成熟的下拉刷新框架,拥有:

  • 12种内置刷新头(Header)和8种加载尾(Footer)样式
  • 完善的嵌套滚动处理机制
  • 二级刷新、越界回弹等高级特性
  • 经过10万+App验证的稳定性

将两者结合,既能享受Compose的开发效率,又能获得SmartRefreshLayout的强大功能。

集成挑战分析

挑战类型具体问题解决方案
架构差异XML布局与Compose声明式UI的混合使用使用AndroidView包装原生视图
状态管理Compose状态与原生视图状态同步实现状态监听与回调转发
嵌套滚动Compose滚动组件与SmartRefreshLayout的事件冲突自定义ScrollBoundaryDecider
主题适配Material Design 3主题与原生组件样式统一开发主题桥接工具类
性能优化避免不必要的重组和视图创建使用remember和LaunchedEffect管理生命周期

核心集成方案详解

方案一:基础包装方案(AndroidView)

这是最简单直接的集成方式,利用Compose提供的AndroidView组件包装SmartRefreshLayout的原生视图。

@Composable
fun SmartRefreshLayout(
    modifier: Modifier = Modifier,
    header: RefreshHeader? = null,
    footer: RefreshFooter? = null,
    onRefresh: (() -> Unit)? = null,
    onLoadMore: (() -> Unit)? = null,
    content: @Composable () -> Unit
) {
    AndroidView(
        modifier = modifier,
        factory = { context ->
            // 创建SmartRefreshLayout实例
            val refreshLayout = com.scwang.smart.refresh.layout.SmartRefreshLayout(context).apply {
                // 设置刷新头和加载尾
                header?.let { setRefreshHeader(it) }
                footer?.let { setRefreshFooter(it) }
                // 设置监听器
                setOnRefreshListener {
                    onRefresh?.invoke()
                }
                setOnLoadMoreListener {
                    onLoadMore?.invoke()
                }
                // 添加Compose内容容器
                addView(FrameLayout(context).apply {
                    id = View.generateViewId()
                })
            }
            refreshLayout
        },
        update = { refreshLayout ->
            // 更新Compose内容
            val container = refreshLayout.findViewById<FrameLayout>(refreshLayout.getChildAt(0).id)
            container.removeAllViews()
            container.addView(AndroidView(
                factory = { ComposeView(it).apply { setContent(content) } }
            ))
        }
    )
}

使用示例

SmartRefreshLayout(
    modifier = Modifier.fillMaxSize(),
    header = ClassicsHeader(context),
    footer = ClassicsFooter(context),
    onRefresh = { 
        // 执行刷新逻辑
        viewModel.refreshData()
    },
    onLoadMore = {
        // 执行加载更多逻辑
        viewModel.loadMoreData()
    }
) {
    LazyColumn {
        items(viewModel.dataList) { item ->
            ItemCard(item)
        }
    }
}

优缺点分析

  • ✅ 实现简单,易于理解和快速上手
  • ✅ 兼容性好,支持所有SmartRefreshLayout功能
  • ❌ 状态同步需要额外代码
  • ❌ 可能导致不必要的视图重建

方案二:高级封装方案(自定义Layout)

该方案基于Compose的Layout API,实现更精细的控制和更好的性能。

@Composable
fun ComposeRefreshLayout(
    modifier: Modifier = Modifier,
    state: RefreshState = rememberRefreshState(),
    header: @Composable (() -> Unit)? = null,
    footer: @Composable (() -> Unit)? = null,
    content: @Composable () -> Unit
) {
    // 使用Layout组件自定义测量和布局逻辑
    Layout(
        modifier = modifier
            .pointerInput(Unit) {
                // 处理触摸事件
                detectVerticalDragGestures(
                    onDragStart = { state.onDragStart() },
                    onDrag = { change, dragAmount ->
                        state.onDrag(dragAmount)
                        change.consume()
                    },
                    onDragEnd = { state.onDragEnd() }
                )
            },
        content = {
            // 刷新头
            if (state.isShowHeader) {
                header?.invoke()
            }
            
            // 内容区域
            Box(modifier = Modifier
                .layoutId("content")
                .offset(y = state.contentOffset.dp)) {
                content()
            }
            
            // 加载尾
            if (state.isShowFooter) {
                footer?.invoke()
            }
        }
    ) { measurables, constraints ->
        // 测量和布局实现
        // ...省略具体实现代码...
    }
}

// 刷新状态管理类
class RefreshState(
    var isRefreshing: Boolean = false,
    var isLoading: Boolean = false,
    var contentOffset: Float = 0f,
    // ...其他状态变量...
) {
    // 状态更新方法
    fun onDrag(dragAmount: Float) {
        // 处理拖动逻辑
    }
    
    fun finishRefresh() {
        // 完成刷新逻辑
    }
    
    fun finishLoadMore() {
        // 完成加载更多逻辑
    }
    
    // ...其他状态管理方法...
}

@Composable
fun rememberRefreshState(): RefreshState {
    return remember { RefreshState() }
}

优缺点分析

  • ✅ 完全基于Compose API,性能更优
  • ✅ 更好的状态管理和重组控制
  • ❌ 实现复杂,需要处理大量触摸事件和布局逻辑
  • ❌ 部分高级功能(如二级刷新)实现难度大

方案三:混合增强方案(原生+Compose组件)

结合前两种方案的优点,使用原生SmartRefreshLayout处理核心逻辑,同时支持Compose实现的刷新头和加载尾。

// 1. 创建Compose刷新头包装类
class ComposeRefreshHeader(
    context: Context,
    private val content: @Composable () -> Unit
) : RefreshHeader(context) {
    
    private var composeView: ComposeView? = null
    
    override fun getView(): View {
        if (composeView == null) {
            composeView = ComposeView(context).apply {
                setContent { content() }
            }
        }
        return composeView!!
    }
    
    // 实现必要的刷新头方法
    override fun onPulling(percent: Float, offset: Int, headerHeight: Int, maxDragHeight: Int) {
        // 处理下拉事件
    }
    
    override fun onReleasing(percent: Float, offset: Int, headerHeight: Int, maxDragHeight: Int) {
        // 处理释放事件
    }
    
    override fun onRefreshing(releaseInstantly: Boolean) {
        // 处理刷新中事件
    }
    
    // ...其他必要方法实现...
}

// 2. 在Compose中使用
@Composable
fun SmartRefreshWithComposeHeader(
    modifier: Modifier = Modifier,
    onRefresh: () -> Unit,
    content: @Composable () -> Unit
) {
    AndroidView(
        modifier = modifier,
        factory = { context ->
            com.scwang.smart.refresh.layout.SmartRefreshLayout(context).apply {
                // 设置自定义Compose刷新头
                setRefreshHeader(ComposeRefreshHeader(context) {
                    // Compose实现的刷新头UI
                    Column(
                        modifier = Modifier.fillMaxWidth().padding(16.dp),
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        CircularProgressIndicator(
                            modifier = Modifier.size(32.dp),
                            color = MaterialTheme.colorScheme.primary,
                            strokeWidth = 2.dp
                        )
                        Spacer(modifier = Modifier.height(8.dp))
                        Text(
                            text = "刷新中...",
                            style = MaterialTheme.typography.bodyMedium,
                            color = MaterialTheme.colorScheme.onSurface
                        )
                    }
                })
                
                setOnRefreshListener {
                    onRefresh()
                }
                
                addView(ComposeView(context).apply {
                    setContent(content)
                })
            }
        }
    )
}

优缺点分析

  • ✅ 兼顾原生框架稳定性和Compose UI灵活性
  • ✅ 支持自定义Compose刷新头/尾,保持UI风格统一
  • ❌ 需要编写额外的包装类
  • ❌ 状态同步逻辑较复杂

方案对比与选型建议

评估维度基础包装方案高级封装方案混合增强方案
实现复杂度⭐⭐☆☆☆⭐⭐⭐⭐⭐⭐⭐⭐☆☆
功能完整性⭐⭐⭐⭐⭐⭐⭐⭐☆☆⭐⭐⭐⭐☆
性能表现⭐⭐⭐☆☆⭐⭐⭐⭐⭐⭐⭐⭐⭐☆
UI一致性⭐⭐☆☆☆⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
学习成本⭐⭐☆☆☆⭐⭐⭐⭐☆⭐⭐⭐☆☆
适用场景快速原型、简单页面性能敏感、定制化高的场景大多数生产环境

选型建议

  • 新项目且团队熟悉Compose:优先考虑高级封装方案
  • 从XML迁移的项目:先使用基础包装方案,逐步过渡到混合增强方案
  • 对稳定性要求极高的商业项目:推荐混合增强方案

实战案例:构建高性能刷新列表

案例1:基础下拉刷新列表

@Composable
fun BasicRefreshList(
    viewModel: ArticleViewModel,
    modifier: Modifier = Modifier
) {
    val context = LocalContext.current
    val articles by viewModel.articles.observeAsState(emptyList())
    val isRefreshing by viewModel.isRefreshing.observeAsState(false)
    val isLoadingMore by viewModel.isLoadingMore.observeAsState(false)
    
    SmartRefreshLayout(
        modifier = modifier.fillMaxSize(),
        header = ClassicsHeader(context).apply {
            setPrimaryColorId(MaterialTheme.colorScheme.primary.toArgb())
            setAccentColorId(android.R.color.white)
        },
        footer = ClassicsFooter(context),
        onRefresh = {
            viewModel.refreshArticles()
        },
        onLoadMore = {
            viewModel.loadMoreArticles()
        }
    ) {
        LazyColumn {
            items(articles) { article ->
                ArticleItem(article = article)
                Divider()
            }
        }
        
        // 状态同步
        LaunchedEffect(isRefreshing) {
            if (!isRefreshing) {
                // 通知刷新完成
                findSmartRefreshLayout().finishRefresh()
            }
        }
        
        LaunchedEffect(isLoadingMore) {
            if (!isLoadingMore) {
                // 通知加载更多完成
                findSmartRefreshLayout().finishLoadMore()
            }
        }
    }
}

案例2:二级刷新(淘宝二楼效果)

@Composable
fun TwoLevelRefreshDemo() {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()
    val twoLevelState = remember { mutableStateOf(false) }
    
    SmartRefreshLayout(
        modifier = Modifier.fillMaxSize(),
        header = TwoLevelHeader(context).apply {
            setOnTwoLevelListener {
                twoLevelState.value = true
                scope.launch {
                    delay(3000) // 模拟加载
                    twoLevelState.value = false
                    it.finishTwoLevel()
                }
                true
            }
        }
    ) {
        // 主内容
        LazyColumn {
            item {
                // 头部Banner
                BannerView()
            }
            items(10) { index ->
                ListItem(
                    headlineText = { Text("列表项 $index") },
                    supportingText = { Text("这是二级刷新演示的列表内容") },
                    leadingContent = { Icon(Icons.Default.Info, contentDescription = null) }
                )
            }
        }
        
        // 二级面板
        if (twoLevelState.value) {
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .background(MaterialTheme.colorScheme.surface)
            ) {
                Column(
                    modifier = Modifier.fillMaxSize(),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.Center
                ) {
                    CircularProgressIndicator()
                    Spacer(modifier = Modifier.height(16.dp))
                    Text("二级面板内容加载中...")
                }
            }
        }
    }
}

案例3:嵌套滚动布局

@Composable
fun NestedScrollDemo() {
    val context = LocalContext.current
    val scrollState = rememberScrollState()
    
    SmartRefreshLayout(
        modifier = Modifier.fillMaxSize(),
        // 自定义滚动边界判断器
        scrollBoundaryDecider = { content, direction ->
            // 当内容不能向上滚动时允许下拉刷新
            direction == ScrollDirection.UP && !content.canScrollVertically(-1)
        }
    ) {
        Column(modifier = Modifier.verticalScroll(scrollState)) {
            // 可折叠工具栏
            TopAppBar(
                title = { Text("嵌套滚动演示") },
                scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
            )
            
            // 内容区域
            Box(modifier = Modifier
                .fillMaxWidth()
                .height(200.dp)
                .background(MaterialTheme.colorScheme.primaryContainer)
            ) {
                Text(
                    "Banner区域",
                    modifier = Modifier.align(Alignment.Center),
                    color = MaterialTheme.colorScheme.onPrimaryContainer
                )
            }
            
            // 列表内容
            LazyColumn(modifier = Modifier.fillMaxWidth()) {
                items(20) { index ->
                    ListItem(
                        headlineText = { Text("嵌套列表项 $index") },
                        leadingContent = { Icon(Icons.Default.Star, contentDescription = null) }
                    )
                }
            }
        }
    }
}

案例4:主题适配实现

@Composable
fun ThemedRefreshLayout(
    content: @Composable () -> Unit
) {
    val context = LocalContext.current
    val colorScheme = MaterialTheme.colorScheme
    val typography = MaterialTheme.typography
    
    // 创建主题适配的刷新头
    val header = remember {
        ClassicsHeader(context).apply {
            setPrimaryColorId(colorScheme.primary.toArgb())
            setAccentColorId(colorScheme.onPrimary.toArgb())
            setTextSizeTitle(
                TypographyUtils.spToPx(typography.titleMedium.fontSize.value, context)
            )
        }
    }
    
    // 创建主题适配的加载尾
    val footer = remember {
        ClassicsFooter(context).apply {
            setPrimaryColorId(colorScheme.surface.toArgb())
            setAccentColorId(colorScheme.onSurface.toArgb())
            setTextSizeTitle(
                TypographyUtils.spToPx(typography.bodyMedium.fontSize.value, context)
            )
        }
    }
    
    SmartRefreshLayout(
        header = header,
        footer = footer,
        content = content
    )
}

// 工具类:将sp转换为px
object TypographyUtils {
    fun spToPx(sp: Float, context: Context): Int {
        return TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_SP,
            sp,
            context.resources.displayMetrics
        ).toInt()
    }
}

案例5:状态管理与生命周期

@Composable
fun StatefulRefreshLayout(
    viewModel: DataViewModel,
    modifier: Modifier = Modifier
) {
    val context = LocalContext.current
    val uiState by viewModel.uiState.observeAsState(UiState.Loading)
    
    // 使用remember保存刷新布局实例
    val refreshLayout = remember { mutableStateOf<SmartRefreshLayout?>(null) }
    
    SmartRefreshLayout(
        modifier = modifier.fillMaxSize(),
        header = BezierRadarHeader(context),
        footer = BallPulseFooter(context),
        onRefresh = {
            viewModel.refreshData()
        },
        onLoadMore = {
            viewModel.loadMoreData()
        },
        onLayoutCreated = { layout ->
            refreshLayout.value = layout
        }
    ) {
        when (uiState) {
            is UiState.Loading -> {
                // 初始加载状态
                LoadingScreen()
            }
            is UiState.Success -> {
                val data = (uiState as UiState.Success).data
                LazyColumn {
                    items(data) { item ->
                        DataItem(item)
                    }
                }
            }
            is UiState.Error -> {
                // 错误状态
                ErrorScreen(
                    message = (uiState as UiState.Error).message,
                    onRetry = { viewModel.refreshData() }
                )
            }
        }
    }
    
    // 状态同步
    LaunchedEffect(uiState) {
        when (uiState) {
            is UiState.Success -> {
                refreshLayout.value?.finishRefresh()
                refreshLayout.value?.finishLoadMore()
            }
            is UiState.Error -> {
                val error = uiState as UiState.Error
                if (error.isRefresh) {
                    refreshLayout.value?.finishRefresh(false)
                } else {
                    refreshLayout.value?.finishLoadMore(false)
                }
            }
            else -> {}
        }
    }
}

性能优化与最佳实践

避免常见性能问题

  1. 减少不必要的重组
// 错误示例:每次重组都会创建新的Header实例
SmartRefreshLayout(
    header = ClassicsHeader(context), // 问题:每次重组创建新实例
    // ...
)

// 正确示例:使用remember缓存实例
val header = remember { ClassicsHeader(context) }
SmartRefreshLayout(
    header = header,
    // ...
)
  1. 优化触摸事件处理
// 使用pointerInputScope处理触摸事件,避免过多事件传递
modifier.pointerInput(Unit) {
    detectVerticalDragGestures(
        onDrag = { change, dragAmount ->
            // 只在需要时消耗事件
            if (state.shouldHandleDrag()) {
                state.onDrag(dragAmount)
                change.consume()
            }
        }
    )
}
  1. 图片资源优化
// 使用Coil加载图片并设置合适的大小
AsyncImage(
    model = ImageRequest.Builder(context)
        .data(item.imageUrl)
        .size(Size.ORIGINAL) // 根据需要调整大小
        .build(),
    contentDescription = null,
    modifier = Modifier.size(120.dp)
)

内存泄漏防护

  1. 正确管理生命周期
@Composable
fun LifecycleAwareRefreshLayout(
    viewModel: MyViewModel,
    content: @Composable () -> Unit
) {
    val refreshLayout = remember { mutableStateOf<SmartRefreshLayout?>(null) }
    val lifecycleOwner = LocalLifecycleOwner.current
    
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_DESTROY -> {
                    // 清理资源
                    refreshLayout.value?.setOnRefreshListener(null)
                    refreshLayout.value?.setOnLoadMoreListener(null)
                }
                else -> {}
            }
        }
        
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
    
    SmartRefreshLayout(
        onLayoutCreated = { layout ->
            refreshLayout.value = layout
        },
        content = content
    )
}
  1. 避免上下文泄漏
// 错误示例:直接持有Activity上下文
val header = ClassicsHeader(LocalContext.current) // 危险!可能导致Activity泄漏

// 正确示例:使用Application上下文
val appContext = LocalContext.current.applicationContext
val header = remember { ClassicsHeader(appContext) }

测试策略

  1. 单元测试
@RunWith(JUnit4::class)
class RefreshStateTest {
    @Test
    fun `test refresh state transitions`() {
        val state = RefreshState()
        
        // 初始状态测试
        assertTrue(!state.isRefreshing)
        
        // 触发刷新
        state.onRefresh()
        assertTrue(state.isRefreshing)
        
        // 完成刷新
        state.finishRefresh()
        assertTrue(!state.isRefreshing)
    }
}
  1. Compose预览测试
@Preview(showBackground = true, name = "Light Mode")
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES, name = "Dark Mode")
@Composable
fun RefreshLayoutPreview() {
    MaterialTheme {
        SmartRefreshLayout(
            modifier = Modifier.fillMaxSize(),
            content = {
                LazyColumn {
                    items(5) {
                        ListItem(headlineText = { Text("预览项 $it") })
                    }
                }
            }
        )
    }
}

常见问题与解决方案

问题1:Compose内容不显示或显示异常

可能原因

  • AndroidView的更新逻辑不正确
  • Compose内容未正确添加到容器中
  • 布局参数设置错误

解决方案

// 确保正确实现update块
AndroidView(
    factory = { context ->
        FrameLayout(context).apply {
            id = View.generateViewId()
        }
    },
    update = { container ->
        // 先清除现有视图
        container.removeAllViews()
        // 添加新的Compose内容
        container.addView(ComposeView(container.context).apply {
            setContent {
                // 你的Compose内容
                Text("Hello Compose")
            }
        })
    }
)

问题2:刷新状态无法同步

可能原因

  • 状态监听逻辑缺失
  • 未正确调用finishRefresh/finishLoadMore
  • 协程作用域管理不当

解决方案

// 使用LaunchedEffect监听状态变化
LaunchedEffect(isRefreshing) {
    if (isRefreshing) {
        // 开始刷新
    } else {
        // 确保在主线程调用
        withContext(Dispatchers.Main) {
            refreshLayout.finishRefresh()
        }
    }
}

问题3:嵌套滚动冲突

可能原因

  • 滚动边界判断不准确
  • 事件消费逻辑不合理
  • Compose滚动组件与原生组件事件冲突

解决方案

// 自定义滚动边界判断器
SmartRefreshLayout(
    scrollBoundaryDecider = { content, direction ->
        when (direction) {
            ScrollDirection.UP -> {
                // 向上滚动时,内容不能再向上滚动则允许刷新
                !content.canScrollVertically(-1)
            }
            ScrollDirection.DOWN -> {
                // 向下滚动时,内容不能再向下滚动则允许加载更多
                !content.canScrollVertically(1)
            }
        }
    }
) {
    // 内容
}

未来展望与进阶方向

Compose原生实现计划

SmartRefreshLayout团队已计划在v3.0版本中提供完整的Compose原生实现,主要改进包括:

  • 完全基于Compose Layout API重构核心逻辑
  • 支持Material Design 3的动态颜色系统
  • 实现更精细的动画控制和状态管理
  • 提供更丰富的组合式API

社区贡献指南

如果你希望为SmartRefreshLayout的Compose支持贡献力量,可以从以下方面入手:

  1. 报告问题:在GitHub Issues提交详细的问题描述和复现步骤
  2. 提交PR:修复bug或实现新功能,遵循项目的代码风格
  3. 文档完善:补充或改进集成文档和示例代码
  4. 案例分享:分享你的集成经验和创新用法

学习资源推荐

  1. 官方文档

  2. 技术文章

    • 《Compose中的自定义布局》
    • 《Android嵌套滚动机制详解》
    • 《Jetpack Compose性能优化实践》
  3. 视频教程

    • Android Developers官方Compose教程
    • SmartRefreshLayout官方YouTube频道

总结

本文详细介绍了SmartRefreshLayout与Jetpack Compose集成的三种方案,从基础包装到高级定制,覆盖了实现原理、代码示例、性能优化和实战案例。无论你是刚开始接触Compose的新手,还是需要将现有项目迁移到Compose的资深开发者,都能从中找到适合自己项目的解决方案。

随着Jetpack Compose生态的不断成熟,我们期待看到更多原生的Compose刷新解决方案。但在那之前,通过本文介绍的集成方案,你可以立即为你的Compose项目带来稳定、强大的下拉刷新功能。

最后,记住最佳的集成方案永远是最适合你的项目需求和团队情况的方案。希望本文能帮助你做出明智的技术决策,构建出色的Android应用。

如果你觉得本文对你有帮助,请点赞、收藏并关注作者,获取更多Android开发进阶内容。下期我们将深入探讨"Compose中的复杂动画实现",敬请期待!

【免费下载链接】SmartRefreshLayout 🔥下拉刷新、上拉加载、二级刷新、淘宝二楼、RefreshLayout、OverScroll,Android智能下拉刷新框架,支持越界回弹、越界拖动,具有极强的扩展性,集成了几十种炫酷的Header和 Footer。 【免费下载链接】SmartRefreshLayout 项目地址: https://gitcode.com/gh_mirrors/smar/SmartRefreshLayout

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值