跳转至

ImagePreviewer

通过ImagePreviewer可以很方便地实现一个类似微信朋友圈放大查看图片的组件

🌭 基本用法

// 准备一个图片列表
val images = remember {
    mutableStateListOf(
        "https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF",
        "https://t7.baidu.com/it/u=4198287529,2774471735&fm=193&f=GIF",
    )
}
// 声明一个PreviewerState
val state = rememberPreviewerState(pageCount = { images.size }) { images[it] }
// 创建一个ImagePreviewer
ImagePreviewer(
    state = state,
    imageLoader = { page ->
        val painter = rememberAsyncImagePainter(model = images[page])
        Pair(painter, painter.intrinsicSize)
    }
)

// 展开
state.open()
// 关闭
state.close()

🍔 过渡动效

使用TransformImageView替代Image,点击图片,图片放大并进入到预览列表

Box(modifier = Modifier.fillMaxSize()) {
    Row(
        modifier = Modifier.align(Alignment.Center),
        horizontalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        images.forEachIndexed { index, url ->
            TransformImageView(
                modifier = Modifier
                    .size(120.dp)
                    .clickable {
                        scope.launch {
                            state.enterTransform(index)
                        }
                    },
                imageLoader = {
                    val painter = rememberAsyncImagePainter(model = url)
                    Triple(url, painter, painter.intrinsicSize)
                },
                transformState = state,
            )
        }
    }
}

如上述代码所示,TransformImageViewimageLoader需要返回一个Triple类型的数据, 其中第一个参数为key,第二个为显示的图片数据,第三个为图片的固有大小

⚠️‼️ 注意KeyIndex的一致性

val images = remember {
    mutableStateListOf(
        // key to image
        "001" to R.drawable.img_01,
        "002" to R.drawable.img_02,
    )
}

val state = rememberPreviewerState(
    pageCount = { images.size },
    getKey = { index -> images[index].first } // 获取key
)

images.forEachIndexed { index, image ->
    TransformImageView(
        imageLoader = {
            val painter = painterResource(id = image.second)
            // key model size
            Triple(image.first, painter, painter.intrinsicSize)
        }
    )
}

// index要与key的position一致
state.enterTransform(index)

在同一个界面中,如果存在同一个key同时出现在不同的部位时,此时使用弹出动画会导致动画位置不符合预期的情况,可以通过指定ItemStateMap的方式来解决

val imageIds = remember { listOf(R.drawable.img_03, R.drawable.img_06) }

val itemStateMap01 = remember { mutableStateMapOf<Any, TransformItemState>() }
val previewerState01 = rememberPreviewerState(
    transformItemStateMap = itemStateMap01,
    pageCount = { imageIds.size },
    getKey = { imageIds[it] },
)

val itemStateMap02 = remember { mutableStateMapOf<Any, TransformItemState>() }
val previewerState02 = rememberPreviewerState(
    transformItemStateMap = itemStateMap02,
    pageCount = { imageIds.size },
    getKey = { imageIds[it] },
)

CompositionLocalProvider(LocalTransformItemStateMap provides itemStateMap01) {
    imageIds.forEach {
        ImagePreviewer(
            state = previewerState01,
            imageLoader = {  }
        )
    }
}

CompositionLocalProvider(LocalTransformItemStateMap provides itemStateMap02) {
    imageIds.forEach {
        ImagePreviewer(
            state = previewerState02,
            imageLoader = {  }
        )
    }
}

如果TransformImageView无法满足功能需求时,可以考虑使用TransformItemView,使用方式见文档:Previewer 过渡动效

🥪 编辑图层

ImagePreviewer中,设置previewerLayer来编辑Previewer的图层,通过pageDecoration来控制每一页的显示, 这里需要注意的是,pageDecoration要求返回一个Boolean类型的值,这个值可以通过调用pageDecoration传入的参数innerPage来获得

ImagePreviewer(
    state = state,
    imageLoader = { page ->
        val painter = rememberAsyncImagePainter(model = images[page])
        Pair(painter, painter.intrinsicSize)
    },
    pageDecoration = { _, innerPage ->
        var mounted = false
        // 单独设置每一页的背景颜色
        Box(modifier = Modifier.background(Color.Cyan.copy(0.2F))) {
            // 通过调用页面获取imageLoader的状态
            mounted = innerPage()
            // 设置前景图层
            Box(
                modifier = Modifier
                    .padding(bottom = 48.dp)
                    .size(56.dp)
                    .shadow(4.dp, CircleShape)
                    .background(Color.White)
                    .align(Alignment.BottomCenter),
            ) {
                Text(
                    modifier = Modifier.align(Alignment.Center),
                    fontSize = 36.sp,
                    text = "❤️",
                )
            }
        }
        // 这里需要返回页面的挂载情况
        mounted
    },
    previewerLayer = TransformLayerScope(
        previewerDecoration = { innerPreviewer ->
            // 设置ImagePreviewer的背景颜色
            Box(
                modifier = Modifier
                    .background(Color.Black)
            ) {
                innerPreviewer.invoke()
            }
        }
    ),
)

🌮 类型拓展

ImagePreviewer可以通过ModelProcessor增加model支持的类型,参考文档:ImageViewer 类型拓展

🌯 Previewer

ImagePreviewer是基于Previewer封装而来的,其参数设置、状态控制、手势回调等用法一致,详情可参考文档:Previewer 基本配置