发布时间:2026/8/27 12:37:38
Jetpack Compose : 一学就会的自定义下拉刷新加载更多 前言一个成熟Androider的标志是自定义下拉刷新加载更多自定义下拉刷新你会怎么做因为我这个人比较懒其实就是菜所以直接拿Compose自带的下拉刷新来修改。这里先上效果图第一张是Compose自带的下拉刷新第二张是我们想要的下拉刷新。通过对比我们很轻松找到需要改造的点列表跟随手指滑动指示器样式修改接下来我们看Compose自带的下拉刷新是如何使用的//refreshing下拉刷新状态 //onRefresh下拉刷新回调方法 val state rememberPullRefreshState(refreshing, onRefresh) //设置下拉刷新 Box(Modifier.pullRefresh(state)) { //列表 LazyColumn() { //...省略部分代码... } //下拉刷新指示器 PullRefreshIndicator(refreshing, state, Modifier.align(Alignment.TopCenter)) }想要让列表跟随手指滑动咱们很容易就能联想到指示器。所以先读下指示器的源码看它的滑动是怎么实现的Composable ExperimentalMaterialApi fun PullRefreshIndicator( refreshing: Boolean, state: PullRefreshState, modifier: Modifier Modifier, backgroundColor: Color MaterialTheme.colors.surface, contentColor: Color contentColorFor(backgroundColor), scale: Boolean false ) { Surface( modifier modifier .size(IndicatorSize) //下拉刷新相关代码 .pullRefreshIndicatorTransform(state, scale), shape SpinnerShape, color backgroundColor, elevation if (showElevation) Elevation else 0.dp, ) { 省略部分代码... } }很容易找到pullRefreshIndicatorTransform(state, scale)继续点进去看源码ExperimentalMaterialApi fun Modifier.pullRefreshIndicatorTransform( state: PullRefreshState, scale: Boolean false, ) composed(inspectorInfo debugInspectorInfo { name pullRefreshIndicatorTransform properties[state] state properties[scale] scale }) { var height by remember { mutableStateOf(0) } Modifier .onSizeChanged { height it.height } .graphicsLayer { //原来滑动处理这么的简单 //注意state.position是internal无法直接使用如何处理后面再讲 translationY state.position - height //...省略部分代码... } }接下来我们思考指示器样式问题。指示器说白就是一个动画这里用最简单的帧动画来实现//动画资源id val loadingResId listOf( R.drawable.loading_big_1, R.drawable.loading_big_4, R.drawable.loading_big_7, R.drawable.loading_big_10, R.drawable.loading_big_13, R.drawable.loading_big_16, R.drawable.loading_big_19, ) //取模获得图片id val id state.position % loadingResId.size //通过Image展示 Image( painter painterResource(loadingResId[id.toInt()]), contentDescription null, contentScale ContentScale.Crop, modifier Modifier .size(40.dp, 16.dp) .align(Alignment.TopCenter) //刚才找到的下拉刷新核心代码 .graphicsLayer { // 你不会再思考为什么 * 0.5f吧别急看到后面就清楚啦 translationY state.position * 0.5f } )完整的自定义下拉刷新加载更多代码OptIn(ExperimentalMaterialApi::class) Composable fun T PullRefreshLayout( modifier: Modifier Modifier, contentPadding: PaddingValues PaddingValues(0.dp), verticalArrangement: Arrangement.Vertical Arrangement.Top, refreshing: Boolean, onRefresh: () - Unit, loading: Boolean, onLoad: () - Unit, items: ListT, itemContent: Composable LazyItemScope.(index: Int, item: T) - Unit ) { val loadingResId listOf( R.drawable.loading_big_1, R.drawable.loading_big_4, R.drawable.loading_big_7, R.drawable.loading_big_10, R.drawable.loading_big_13, R.drawable.loading_big_16, R.drawable.loading_big_19, ) //指示器图片高度 val loadingHeightPx: Float with(LocalDensity.current) { loadingHeightPx 16.dp.toPx() } //指示器循环动画 val loadingAnimate by rememberInfiniteTransition().animateFloat( initialValue 0f, targetValue loadingResId.size.toFloat(), animationSpec infiniteRepeatable( animation tween(250, easing LinearEasing), repeatMode RepeatMode.Reverse ) ) //前面说过PullRefreshState.position是internal无法直接使用 //所以我们就把rememberPullRefresh的代码copy过来小改下 val state rememberPullRefreshLayoutState(refreshing, onRefresh) Box(Modifier.pullRefreshLayout(state)) { LazyColumn( //让列表跟随手指滑动 modifier modifier.graphicsLayer { translationY state.position }, contentPadding contentPadding, verticalArrangement verticalArrangement, ) { itemsIndexed(items) { index, item - itemContent(index, item) //自动加载更多这里的触发值是5 if (loading items.size - index 5) { LaunchedEffect(items.size) { onLoad() } } } if (items.isNotEmpty()) { item { //加载更多的样式这里用文本简单显示下 Box(modifier Modifier .fillMaxWidth() .padding(8.dp) .clickable { onLoad() } ) { Text( text , fontSize 12.sp, color Color.Gray, modifier Modifier.align(alignment Alignment.Center) ) } } } } // Custom progress indicator val id if (refreshing) loadingAnimate else state.position % loadingResId.size if (refreshing || (state.position loadingHeightPx * 0.5f)) { Image( painter painterResource(loadingResId[id.toInt()]), contentDescription null, contentScale ContentScale.Crop, modifier Modifier .size(40.dp, 16.dp) .align(Alignment.TopCenter) //让指示器跟随手指滑动 .graphicsLayer { translationY state.position * 0.5f } ) } } } //不用看就改个名字而已 Composable ExperimentalMaterialApi fun rememberPullRefreshLayoutState( refreshing: Boolean, onRefresh: () - Unit, refreshThreshold: Dp PullRefreshDefaults.RefreshThreshold, refreshingOffset: Dp PullRefreshDefaults.RefreshingOffset, ): PullRefreshLayoutState { require(refreshThreshold 0.dp) { The refresh trigger must be greater than zero! } val scope rememberCoroutineScope() val onRefreshState rememberUpdatedState(onRefresh) val thresholdPx: Float val refreshingOffsetPx: Float with(LocalDensity.current) { thresholdPx refreshThreshold.toPx() refreshingOffsetPx refreshingOffset.toPx() } val state remember(scope) { PullRefreshLayoutState(scope, onRefreshState, refreshingOffsetPx, thresholdPx) } SideEffect { state.setRefreshing(refreshing) } return state } //不用看就是改个名字并把position的internal去掉 ExperimentalMaterialApi fun Modifier.pullRefreshLayout( state: PullRefreshLayoutState, enabled: Boolean true ) inspectable(inspectorInfo debugInspectorInfo { name pullRefresh properties[state] state properties[enabled] enabled }) { Modifier.pullRefresh(state::onPull, { state.onRelease() }, enabled) } ExperimentalMaterialApi class PullRefreshLayoutState internal constructor( private val animationScope: CoroutineScope, private val onRefreshState: State() - Unit, private val refreshingOffset: Float, internal val threshold: Float ) { val progress get() adjustedDistancePulled / threshold internal val refreshing get() _refreshing //唯一的变化去掉internal val position get() _position private val adjustedDistancePulled by derivedStateOf { distancePulled * 0.5f } private var _refreshing by mutableStateOf(false) private var _position by mutableStateOf(0f) private var distancePulled by mutableStateOf(0f) internal fun onPull(pullDelta: Float): Float { if (this._refreshing) return 0f val newOffset (distancePulled pullDelta).coerceAtLeast(0f) val dragConsumed newOffset - distancePulled distancePulled newOffset _position calculateIndicatorPosition() return dragConsumed } internal fun onRelease() { if (!this._refreshing) { if (adjustedDistancePulled threshold) { onRefreshState.value() } else { animateIndicatorTo(0f) } } distancePulled 0f } internal fun setRefreshing(refreshing: Boolean) { if (this._refreshing ! refreshing) { this._refreshing refreshing this.distancePulled 0f animateIndicatorTo(if (refreshing) refreshingOffset else 0f) } } private fun animateIndicatorTo(offset: Float) animationScope.launch { animate(initialValue _position, targetValue offset) { value, _ - _position value } } private fun calculateIndicatorPosition(): Float when { adjustedDistancePulled threshold - adjustedDistancePulled else - { val overshootPercent abs(progress) - 1.0f val linearTension overshootPercent.coerceIn(0f, 2f) val tensionPercent linearTension - linearTension.pow(2) / 4 val extraOffset threshold * tensionPercent threshold extraOffset } } }最后写之前有好多东西想要表达真正写的时候又变成贴代码写作能力还有待提高呀这篇文章更多是讲开发思路下拉刷新源码解析没有多少点赞多的话到时候整一篇源码解析哈。Thanks以上就是本篇文章的全部内容如有问题欢迎指出我们一起进步。如果觉得本篇文章对您有帮助的话请点个赞让更多人看到吧您的鼓励是我前进的动力。谢谢~~源代码地址MyCollectFragment.kt · miaowmiaow/fragmject作者miaowmiaow链接https://juejin.cn/post/7185159395519496250最后如果想要成为架构师或想突破20~30K薪资范畴那就不要局限在编码业务要会选型、扩展提升编程思维。此外良好的职业规划也很重要学习的习惯很重要但是最重要的还是要能持之以恒任何不能坚持落实的计划都是空谈。如果你没有方向这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》帮大家将杂乱、零散、碎片化的知识进行体系化的整理让大家系统而高效地掌握Android开发的各个知识点。相对于我们平时看的碎片化内容这份笔记的知识点更系统化更容易理解和记忆是严格按照知识体系编排的。全套视频资料一、面试合集二、源码解析合集三、开源框架合集欢迎大家一键三连支持若需要文中资料直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

相关新闻

2026/8/27 12:37:38

腾讯、阿里团队共同出品,Android音视频开发进阶指南

随着5G时代的到来,短视频逐步占据了人们的日常生活。现在,也有越来越多的公司高薪招聘音视频开发的人才,一个稍有经验的音视频人才现在可能会有3-4家公司抢着要。因此,对音视频人才的需求也从小众变成了大众,这更多的是…

2026/8/27 12:37:38

浅谈Android音视频开发那点事儿,音视频究竟如何入门?

在如今快节奏,网络发达的社会。人们对于静态的图片和文字往往很难有精力去安心完整的阅读完网上内容。为了满足人们可以更生动的阅读网上的内容,也为了使网络交互更加生动。我们便需要应用到音频和视频来增加人们在网络上动作交互的丰富性,音…

2026/8/27 13:27:43

低成本搭建医疗管理系统,低代码平台成企业首选

上个月,我一个开诊所的朋友跟我抱怨,说想上一套管理系统,结果问了一圈,动辄几十万起步,还得等上大半年。他那小本经营,哪扛得住这个?其实不只是他,很多医疗相关企业都有这个痛点。买…

2026/8/27 13:27:43

政企转型必备,程序员力荐的低代码管理平台选型指南

兄弟们,最近是不是被各种“数字化转型”搞得头大?尤其咱们搞技术的,在政企项目里,最怕听到“需求又变了”、“这个月底能上线吗”。传统开发模式,业务部门等不起,咱们开发兄弟也熬不起。我去年就经历过一个…

2026/8/27 13:27:43

基于RWKV的中文网文生成实战:从模型原理到LoRA微调

简介:在自然语言处理与AI写作工具快速演进的今天,生成式模型的长文本建模能力成为核心议题。传统Transformer架构的自注意力机制虽在语义理解上表现出色,却受限于平方级计算复杂度,难以支撑小说、网文等超长文本的高效生成。RWKV作…

2026/8/27 13:27:43

解密prompt系列26. 人类思考vs模型思考:抽象和发散思维

前言 在Chain of Thought出来后,出现过许多的优化方案例如Tree of thought, Graph of Thought, Algorithm of Thought等等,不过这些优化的出发点都更加"Machine Like",而非"Human Like", 哈哈不是说机器化不好,仅仅是对A…

2026/8/27 13:27:43

Physical AI经验工程:构建机器人全链路数据基建的实践指南

之前在机器人项目里做数据闭环时,最头疼的不是模型训练,而是“经验”本身很难被采集、标注、回放和复用。同一个搬运动作,老师傅做出来是丝滑的,模型学出来却是抖动的;同样的导航策略,仿真环境里跑得很好&a…

2026/8/27 13:22:42

数字压力传感器如何提升设计灵活性:接口、封装与选型指南

1. 从模拟到数字:压力传感器这颗料到底帮工程师省了什么做嵌入式硬件这些年,我对传感器选型最大的感触是:一颗料的选择,往往不只是封装和 pin 脚的替换,而是整个设计思路的变化。标题里这句"Digital Pressure Sen…

2026/8/26 9:13:28

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/27 10:58:22

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/27 7:46:21

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/27 0:01:16

Go语言构建企业级AI服务网关:统一管理英伟达等AI接口调用

1. 项目概述:从零构建一个企业级的AI服务网关 最近在帮一个做内容审核的团队做技术架构升级,他们原来的业务里,每天有几十万张图片和短视频需要过审,最初是接了几个开源的AI模型自己部署,但效果和性能一直不太稳定。后…

2026/8/27 0:01:16

LeetCode Hot100(51-60)算法精解与面试技巧

1. 题目背景与核心价值"hot100(51-60)"这个标题看起来像是某个编程题库或算法练习集中的一组题目编号。在技术社区中,类似命名通常指向LeetCode、牛客网等平台的热门题目集合。作为刷过300题的算法老手,我理解这类题目的核心价值在于&#xff…

2026/8/27 0:01:16

CRC校验实战:从模2除法到HJ212协议排错

1. 为什么一个“校验码”能扛住工业现场90%的数据 corruption? 你有没有遇到过这样的场景:嵌入式设备通过RS-485上传温湿度数据,上位机偶尔收到一帧乱码——温度显示成-273℃,湿度跳到999%,但串口波形看起来完全正常&a…

2026/8/26 19:34:06

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/26 19:17:08

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/26 19:34:05

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…