jetpack compose中实现丝滑的轮播图效果

compose中轮播图的实现

功能考虑:

  1. 轮播图的核心组件 :HorizontalPager
  2. 轮播图如何实现自动轮播 LaunchedEffect ,并且轮播附带动画效果
  3. 单击轮播图实现放大查看功能
  4. 长按轮播图 出现删除按钮 ,并附带(放缩,图片阴影等动画效果)

效果

这里可以看到添加图片之后自动识别了第一张图片并填充了名称 字段,这里使用了mlkit的端智能模型,具体可以看TensorFlow Lite 端智能初探 - 掘金 (juejin.cn)

轮播图布局

scss 复制代码
Box(
    modifier = Modifier
        .fillMaxWidth()
        .aspectRatio(16 / 9f)
) {
    HorizontalPager(){index->
        Box(){//1.
            image()//正常图片
            if(显示删除按钮){
            image()//删除按钮
            }
        }
        Row(){//轮播下标
            repeat(picList.size) { index ->
                    Box()
                }
        }
    }
    Row(){//摄像机按钮
        Image()
        }
}

1.轮播图动画设置

ini 复制代码
val imgScale by animateFloatAsState(targetValue = if (pagerState.currentPage == index) 1f else 0.8f,
    animationSpec = tween(300)
)
 modifier = Modifier.scale(imgScale)//设置给轮播图的Box()

2.轮播下标 根据轮播位置变化

这里只需要 监听pagerState.currentPage 的值,之后让compose自动重组就可以了

scss 复制代码
modifier = Modifier
    .padding(start = 2.dp, end = 2.dp)
    .width(if (index == pagerState.currentPage) 12.dp else 4.dp)
    .height(4.dp)
    .clip(if (index == pagerState.currentPage) RoundedCornerShape(2.dp) else CircleShape)
    .background(
        color = if (index == pagerState.currentPage) Color(
            0xff3988FF
        ) else Color(
            0xffE4E5E7
        ),
        shape = if (index == pagerState.currentPage) RoundedCornerShape(
            2.dp
        ) else CircleShape
    )

3.如何实现自动轮播

在传统的view下,一般实现轮播图会使用定时器来轮播

但是Compose会根据变更的状态重新绘制相关的组件,在LaunchedEffect中执行的滚动操作会导致pagerState.settledPage的变化,从而触发Compose的重组,最终实现了轮播效果。

scss 复制代码
if (auto&&picList.size!=1) {
    LaunchedEffect(pagerState.settledPage) {
            delay(gapTime)
            if (!isLongClick) {
                if (pagerState.currentPage == picList.size - 1) {
                    pagerState.animateScrollToPage(0)
                } else {
                    pagerState.animateScrollToPage(pagerState.currentPage + 1)
                }
            }
​
    }
}

这里选择用pagerState.settledPage 而不是pagerState.currentPage 原因是因为它不会在页面滚动时更新,而是在动画滚动稳定时更新。这就避免了自动轮播跟我们手势冲突的发生

4.如何实现长按删除的动画效果

在Jetpack Compose中,graphicsLayer 是一个用于在组合界面中进行图形处理和动画的工具。它属于Compose的图形处理系统,用于对组合元素应用图形变换、动画等效果。

graphicsLayer 主要用于在组合中添加、修改或移除图形层,以实现一些高级的图形和动画效果。

ini 复制代码
val  shadowElevation by animateFloatAsState(targetValue = if (isLongClick) 8f else 0f,
                       animationSpec = tween(200)
                    )
val scaleElevation by animateFloatAsState(targetValue = if (isLongClick) 0.8f else 1f,
                        animationSpec = tween(200)
                    )
val alphaElevation by animateFloatAsState(targetValue = if (isLongClick) 0.6f else 1f,
                        animationSpec = tween(200)
                    )
Modifier.graphicsLayer(
    alpha = alphaElevation,
    translationX = 0f,
    translationY = 0f,
    scaleX = scaleElevation,
    scaleY = scaleElevation,
    shadowElevation = shadowElevation
)

这样我们只要在长按/单击事件中实现逻辑改变 isLongClick变量即可,具体如下

5.具体完整代码

ini 复制代码
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PagerOne(viewModel: NewAndModifyActivityViewModel, auto: Boolean = true, gapTime: Long = 3000L, picList : SnapshotStateList<Image>, onItemClick :(index:Int)->Unit) {
​
    val pagerState = rememberPagerState(initialPage = 0)
​
​
    var isLongClick by remember {
        mutableStateOf(false)
    }
    if (auto&&picList.size!=1) {
        LaunchedEffect(pagerState.settledPage) {
                delay(gapTime)
                if (!isLongClick) {
                    if (pagerState.currentPage == picList.size - 1) {
                        pagerState.animateScrollToPage(0)
                    } else {
                        pagerState.animateScrollToPage(pagerState.currentPage + 1)
                    }
                }
​
        }
    }
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(16 / 9f)
    ) {
        HorizontalPager(
            pageSpacing = 0.dp,
            pageCount = picList.size,
            state = pagerState,
            contentPadding = PaddingValues(horizontal = 50.dp),
            modifier = Modifier
                .fillMaxSize()
        ) { index ->
            val imgScale by animateFloatAsState(targetValue = if (pagerState.currentPage == index) 1f else 0.8f,
                animationSpec = tween(300)
            )
            Box(
                modifier = Modifier
                    .scale(imgScale)
                    .padding(vertical = 5.dp)
                    .background(Color.White, shape = RoundedCornerShape(20.dp))
                    .fillMaxSize()
                    .align(Alignment.Center),
                contentAlignment = Alignment.BottomEnd
            ) {
                if (picList.size!=0) {
                    val  shadowElevation by animateFloatAsState(targetValue = if (isLongClick) 8f else 0f,
                        animationSpec = tween(200)
                    )
                    val scaleElevation by animateFloatAsState(targetValue = if (isLongClick) 0.8f else 1f,
                        animationSpec = tween(200)
                    )
                    val alphaElevation by animateFloatAsState(targetValue = if (isLongClick) 0.6f else 1f,
                        animationSpec = tween(200)
                    )
                    Image(
                        painter = rememberAsyncImagePainter(picList[index].url),
                        contentDescription = null,
                        contentScale = ContentScale.Crop,
                        modifier = Modifier
                            .fillMaxSize()
                            .padding( 10.dp)
                            .pointerInput(Unit) {
                                detectTapGestures(
                                    onLongPress = {
                                        if (viewModel.isEnable) {
                                            isLongClick = true//停止轮播
​
                                        }
                                    }, onTap = {
                                        if (isLongClick) {
                                            isLongClick = false
                                        } else {
                                            onItemClick(index)//实现查看图片逻辑
                                        }
                                    }
                                )
                            }
                            .graphicsLayer(//设置长按后的动画
                                alpha = alphaElevation,
                                translationX = 0f,
                                translationY = 0f,
                                scaleX = scaleElevation,
                                scaleY = scaleElevation,
                                shadowElevation = shadowElevation
                            )
​
                    )
                    if (isLongClick){
                        //删除按钮出现的动画
                        val deleteScale by animateFloatAsState(targetValue = if (isLongClick) 1.5f else 0.1f,
                            animationSpec =spring(0.1f, Spring.StiffnessMedium)
                        )
                        Image(
                            painter = painterResource(id = R.drawable.delete_add),
                            contentDescription = "image description",
                            contentScale = ContentScale.Fit,
                            modifier =if(viewModel.isEnable) {
                                Modifier
                                    .size(50.dp)
                                    .align(Alignment.Center)
                                    .clip(RoundedCornerShape(50.dp))
                                    .scale(deleteScale)
                                    .clickable {
                                        picList.removeAt(pagerState.currentPage)
                                        if (picList.size == 0) {
                                            viewModel.isPicAdded = false
                                        }
                                        isLongClick = false
                                    }
                            }else {
                                Modifier
                                    .size(35.dp)
                                    .scale(deleteScale)
                                    .clip(RoundedCornerShape(4.dp))
                            })
                    }
                }
            }
            Row(
                modifier = Modifier
                    //.fillMaxWidth()
                    .padding(bottom = 10.dp)
                    .align(Alignment.BottomCenter),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically
            ) {
                repeat(picList.size) { index ->
                    Box(
                        modifier = Modifier
                            .padding(start = 2.dp, end = 2.dp)
                            .width(if (index == pagerState.currentPage) 12.dp else 4.dp)
                            .height(4.dp)
                            .clip(if (index == pagerState.currentPage) RoundedCornerShape(2.dp) else CircleShape)
                            .background(
                                color = if (index == pagerState.currentPage) Color(
                                    0xff3988FF
                                ) else Color(
                                    0xffE4E5E7
                                ),
                                shape = if (index == pagerState.currentPage) RoundedCornerShape(
                                    2.dp
                                ) else CircleShape
                            )
                    )
                }
            }
​
​
        }
        Row(
            modifier = Modifier
                .width(90.dp)
                .width(45.dp)
                .align(Alignment.BottomEnd),
        ) {
​
            Image(
                painter = painterResource(id = R.drawable.add_pic_add),
                contentDescription = "image description",
                contentScale = ContentScale.Fit,
                modifier = if (viewModel.isEnable){
                    Modifier
                        .padding(start = 3.dp)
                        .size(35.dp)
                        .clip(RoundedCornerShape(4.dp))
                        .clickable {
                            //此处是从相机获取照片的逻辑
                        }
                }else{
                    Modifier
                        .padding(start = 3.dp)
                        .size(35.dp)
                        .clip(RoundedCornerShape(4.dp))
                }
            )
        }
    }
​
}
相关推荐
OkeyProxy9 小时前
設置Android設備全局代理
android·代理模式·proxy模式·代理服务器·海外ip代理
刘志辉10 小时前
vue传参方法
android·vue.js·flutter
前期后期12 小时前
Android OkHttp源码分析(一):为什么OkHttp的请求速度很快?为什么可以高扩展?为什么可以高并发
android·okhttp
轻口味15 小时前
Android应用性能优化
android
全职计算机毕业设计15 小时前
基于 UniApp 平台的学生闲置物品售卖小程序设计与实现
android·uni-app
dgiij15 小时前
AutoX.js向后端传输二进制数据
android·javascript·websocket·node.js·自动化
SevenUUp16 小时前
Android Manifest权限清单
android
高林雨露16 小时前
Android 检测图片抓拍, 聚焦图片后自动完成拍照,未对准图片的提示请将摄像头对准要拍照的图片
android·拍照抓拍
wilanzai16 小时前
Android View 的绘制流程
android
INSBUG17 小时前
CVE-2024-21096:MySQLDump提权漏洞分析
android·adb