前言
回顾下之前的内容,我们已经实现了以下相关页内容,它们分别是:
Jetpack Compose 实战之仿微信UI -实现登陆页(一)
Jetpack Compose 实战之仿微信UI -实现首页(二)
Jetpack Compose 实战之仿微信UI -实现朋友圈(三)
在这一篇文章中,我将使用 Jetpack Compose 去实现微信朋友圈图片的预览。
功能拆分
微信朋友圈图片预览的功能主要包括:
(1)图片左右滑动
(2)页面指示器
(3)缩放
(4)双击放大(然后再双击恢复,反之)
(5)单击返回
功能实现
图片左右滑动
在实现首页的时候,我们使用了 HorizontalPager 进行页面切换,所以我们在这里直接使用HorizontalPager作为图片左右切换的组件即可,实现的代码为:
ini
HorizontalPager(
count = images.size,
state = pageState,
contentPadding = PaddingValues(horizontal = 0.dp),
modifier = Modifier.fillMaxSize()
) { page ->
ImagePageItem()
}
页面指示器
HorizontalPager给我们封装了一个指示器组件 HorizontalPagerIndicator,我们直接使用就能实现我们的效果,实现代码为:
ini
HorizontalPagerIndicator(
pagerState = pageState,
activeColor = Color.White, /*** 指示器选中的颜色 */
inactiveColor = Color(0xff888888), /*** 指示器未选中的颜色 */
indicatorHeight = 6.dp, /*** 指示器的高度 */
indicatorWidth = 6.dp, /*** 指示器的宽度 */
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(60.dp)
)
到这里,我们就实现了图片的切换以及指示器部分了,看下效果
缩放
缩放实现的核心是监听到手势的动作,然后拿到缩放比例值,平移偏移量等数据,通过查找资料,我发现官方给我们提供了Modifier的拓展函数 transformable,我们看下transformable这个拓展函数的结构:
ini
Modifier.transformable(
state: TransformableState,
lockRotationOnZoomPan: Boolean = false,
enabled: Boolean = true
)
各属性的含义:
state: 监听手势变化
lockRotationOnZoomPan: 如果为true,则仅当在平移或缩放运动之前检测到旋转的触摸倾斜时才允许旋转。否则,将检测到平移和缩放手势,但不会检测到旋转手势。如果为false,一旦达到触摸斜率,将检测所有三个手势。
enabled: 是否启用手势缩放
所以,在这里我们通过state的监听就可以拿到缩放比例值,平移偏移量等数据,具体的代码为:
scss
/*** 缩放比例 */
var scale by remember { mutableStateOf(1f) }
/*** 偏移量 */
var offset by remember { mutableStateOf(Offset.Zero) }
/*** 监听手势变化 */
val state = rememberTransformableState(onTransformation = { zoomChange, offsetChange, rotationChange ->
scale = (zoomChange * scale).coerceAtLeast(1f)
scale = if (scale > 4f) {
4f
} else {
scale
}
offset += offsetChange
})
其中 rotationChange 是旋转的变化,这里我们不需要,就不处理。4f 是我们允许放大的最大倍数,根据自己的需要自定义即可。
到这里,我们将 state 赋值到 Image 组件的 Modifier,就可以监听到图片的手势的缩放,平移等,具体代码为:
ini
Image(
painter = rememberCoilPainter(
request = image
),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier.transformable(state = state)
)
但是,我们拿到了手势变化的数据,怎样控制图片随着这些数据进行动画呢?官方提供了动画属性 Modifier.graphicsLayer ,通过 GraphicsLayerScope 来控制缩放,平移,旋转,透明度,渐变等常用动画,具体的实现代码为:
ini
Image(
painter = rememberCoilPainter(
request = image
),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.transformable(state = state) /** 检测手势元素的平移、缩放、旋转 */
.graphicsLayer{ /**缩放、旋转、移动变换 */
scaleX = scale /** 等比缩放 */
scaleY = scale /** 等比缩放 */
translationX = offset.x /** X轴位移量*/
translationY = offset.y /** Y轴位移量*/
/** 计算页面的当前偏移 */
val pageOffset = pagerScope.calculateCurrentOffsetForPage(page = page).absoluteValue
if (pageOffset == 1.0f) {
scale = 1.0f
}
)
这样,我们监听手势的变化和使用动画就达到了图片缩放,平移的效果了。
双击,单击时事件的处理
我们使用 pointerInput 来监听图片的单双击事件,具体的代码为:
ini
pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
scale = if (scale <= 1f) {
2f
} else {
1f
}
offset = Offset.Zero
},
onTap = {
context.finish()
}
)
}
(1)单击的处理 :比较简单,就是直接finish当前页面,如果是使用 navigation 导航的,使用navController.popBackStack()即可,我这里是使用Activity。
(2)双击的处理:这里的只有放大和正常两种状态,当前状态为正常时,双击就放大2倍(自定义,当然也可以放大3倍),当前状态为放大时,双击就将缩放倍数改为1倍,这样那就达到了我们的效果。
实现的全部代码为:
ini
@OptIn(ExperimentalPagerApi::class)
@Composable
fun ImagePreviewScreen(images: ArrayList<String>, currentIndex: Int) {
rememberSystemUiController().setStatusBarColor(Color.Black, darkIcons = true)
/*** 界面状态变更 */
val pageState = rememberPagerState(initialPage = currentIndex)
Box {
/*** 图片部分 */
HorizontalPager(
count = images.size,
state = pageState,
contentPadding = PaddingValues(horizontal = 0.dp),
modifier = Modifier.fillMaxSize()
) { page ->
ImagePageItem(images[page], page, this)
}
/*** 指示器部分 */
HorizontalPagerIndicator(
pagerState = pageState,
activeColor = Color.White, /*** 指示器选中的颜色 */
inactiveColor = Color(0xff888888), /*** 指示器未选中的颜色 */
indicatorHeight = 6.dp, /*** 指示器的高度 */
indicatorWidth = 6.dp, /*** 指示器的宽度 */
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(60.dp)
)
}
}
@OptIn(ExperimentalPagerApi::class)
@Composable
fun ImagePageItem(
image: String,
page: Int = 0,
pagerScope: PagerScope
) {
val context = LocalContext.current as Activity
/*** 缩放比例 */
var scale by remember { mutableStateOf(1f) }
/*** 偏移量 */
var offset by remember { mutableStateOf(Offset.Zero) }
/*** 监听手势变化 */
val state = rememberTransformableState(onTransformation = { zoomChange, offsetChange, rotationChange ->
scale = (zoomChange * scale).coerceAtLeast(1f)
scale = if (scale > 4f) {
4f
} else {
scale
}
offset += offsetChange
})
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.Black,
) {
Image(
painter = rememberCoilPainter(
request = image
),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.transformable(state = state) /** 检测手势元素的平移、缩放、旋转 */
.graphicsLayer{ /**缩放、旋转、移动变换 */
scaleX = scale /** 等比缩放 */
scaleY = scale /** 等比缩放 */
translationX = offset.x /** X轴位移量*/
translationY = offset.y /** Y轴位移量*/
/** 计算页面的当前偏移 */
val pageOffset = pagerScope.calculateCurrentOffsetForPage(page = page).absoluteValue
if (pageOffset == 1.0f) {
scale = 1.0f
}
}.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
scale = if (scale <= 1f) {
2f
} else {
1f
}
offset = Offset.Zero
},
onTap = {
context.finish()
}
)
}
)
}
}
到这里,我们就实现了仿微信朋友圈图片的预览功能了,一起来看下全部效果
总结
我们这一期使用了 HorizontalPager 实现了图片切换,使用 HorizontalPagerIndicator 实现了指示器的功能,使用 transformable 监听手势的缩放,平移等动作,使用了 graphicsLayer 进行动画,使用 pointerInput 来监听图片的单双击事件。
项目地址:ComposeWechat,如果对你有用,别忘了给个star