compose中轮播图的实现
功能考虑:
- 轮播图的核心组件 :HorizontalPager
- 轮播图如何实现自动轮播 LaunchedEffect ,并且轮播附带动画效果
- 单击轮播图实现放大查看功能
- 长按轮播图 出现删除按钮 ,并附带(放缩,图片阴影等动画效果)
效果
这里可以看到添加图片之后自动识别了第一张图片并填充了名称 字段,这里使用了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))
}
)
}
}
}