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

相关推荐
alexhilton6 天前
Android技巧:学习使用GridLayout
android·kotlin·android jetpack
Wgllss13 天前
轻松搞定Android蓝牙打印机,双屏异显及副屏分辨率适配解决办法
android·架构·android jetpack
alexhilton20 天前
群星闪耀的大前端开发
android·kotlin·android jetpack
一航jason1 个月前
Android Jetpack Compose 现有Java老项目集成使用compose开发
android·java·android jetpack
帅次1 个月前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
IAM四十二1 个月前
Jetpack Compose State 你用对了吗?
android·android jetpack·composer
Wgllss1 个月前
那些大厂架构师是怎样封装网络请求的?
android·架构·android jetpack
x0242 个月前
Android Room(SQLite) too many SQL variables异常
sqlite·安卓·android jetpack·1024程序员节
alexhilton2 个月前
深入理解观察者模式
android·kotlin·android jetpack
Wgllss2 个月前
花式高阶:插件化之Dex文件的高阶用法,极少人知道的秘密
android·性能优化·android jetpack