Jetpack Compose 实战之仿微信UI -实现朋友圈图片预览(四)

前言

回顾下之前的内容,我们已经实现了以下相关页内容,它们分别是:

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

相关推荐
x02414 天前
Android Room(SQLite) too many SQL variables异常
sqlite·安卓·android jetpack·1024程序员节
alexhilton17 天前
深入理解观察者模式
android·kotlin·android jetpack
Wgllss17 天前
花式高阶:插件化之Dex文件的高阶用法,极少人知道的秘密
android·性能优化·android jetpack
上官阳阳20 天前
使用Compose创造有趣的动画:使用Compose共享元素
android·android jetpack
沐言人生24 天前
Android10 Framework—Init进程-15.属性变化控制Service
android·android studio·android jetpack
IAM四十二1 个月前
Android Jetpack Core
android·android studio·android jetpack
王能1 个月前
Kotlin真·全平台——Kotlin Compose Multiplatform Mobile(kotlin跨平台方案、KMP、KMM)
android·ios·kotlin·web·android jetpack·kmp·kmm
alexhilton1 个月前
让Activity更加优雅地跳转
android·kotlin·android jetpack
沐言人生1 个月前
Android10 Framework—Init进程-11.客户端操作属性
android·android studio·android jetpack
沐言人生1 个月前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack