ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Jetpack Compose : 一学就会的自定义下拉刷新加载更多

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官方认证微信卡片免费领取↓↓↓
返回列表