实现截图分享功能的那些坎坎坷坷

89 阅读4分钟

一、需求是什么呢?

将界面上呈现的部分内容,生成图片分享到社交媒体

二、实现方案

1、将分享内容显示到一个底部弹窗上,这里使用的是Compose UI 做的(后面会说到这里的坑),弹窗使用的是ModalBottomSheet

2、使用view.drawToBitmap,将显示的View 绘制到bitmap上。这里会使用到一个方法Modifier.onGloballyPositioned,获取到当前需要截图内容的区域。

 modifier.onGloballyPositioned {
    onScreenshotBounds(it.boundsInWindow())
}

3、将bitmap存储到本地并生成Uri,这样第三方平台就可以分享这张图片

三、实现过程中遇到的各种问题

实现方案是完全没有问题的,但是实际做的时候遇到两个各种阻碍

1、分享到第三方平台,这里需要选择具体分享到哪个平台,这是一个已经实现的组件,使用的是BottomSheetDialogFragment,分享内容预览的内容也是一个弹窗,是用的Compose 的ModalBottomSheet实现的,也就是会出现这种情况:选择分享平台的弹窗是在分享内容预览的弹窗智之上,而且是用原生View和Compose 混合实现的。这里出的一个bug是,当屏幕旋转时,弹窗恢复后,分享预览的弹窗,也就是ModalBottomSheet会置于顶部,分享平台选择的弹窗会被置于底部

关于这个bug,最开始以为是BottomSheetDialogFragment在屏幕旋转后没有恢复状态,但是经过测试后,发现并不是这样的,而是因为被顶部的ModalBottomSheet遮挡了,看起来是消失了。

解决方案: 最终选择了将分享内容预览的弹窗也放到了BottomSheetDialogFragment中,这样就可以解决掉上面的那个bug

2、使用BottomSheetDialogFragment实现分享预览:

问题1:弹窗没有按内容全屏显示,而是折叠了

解决方法:重写onCreateDialog

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return BottomSheetDialog(requireContext(), R.style.BottomSheetDialogTheme).apply {
        behavior.skipCollapsed = true
        behavior.state = BottomSheetBehavior.STATE_EXPANDED
    }
}

问题2:分享内容时可滑动,和弹窗的下拉隐藏,手势有冲突,导致下滑内容时,弹窗隐藏,内容无法下滑

解决方法:未可滑动的组件添加nestedScroll,解决滑动冲突。这里需要补充一下关于Compose View 对滑动冲突对的解决方案,看起来有点复杂,需要进一步去研究学习。

Column(
    modifier = Modifier
        .fillMaxWidth()
        .nestedScroll(rememberNestedScrollInteropConnection())
        .verticalScroll(rememberScrollState())
)

问题3:非常棘手的,可滑动的Compose View 无法截图到未显示的内容

解决方法:

查阅了相关资料后,包括询问AI 解决方案。最多的一种方式是使用离屏渲染,也就是将要截图的内容在看不到的地方重新全部渲染出来,然后进行截图到bitmap。还有一种方式就是使用原生的可滑动View,NestedScrollView。我之前做过长截图分享,当时所要分享的内容就是可滑动,就是用的NestedScrollView实现的,因此我采用了这种方式,将用Compose UI 实现的分享内容放到NestedScrollView中,因为我觉得离屏渲染性能不好,而且实现起来较为复杂。

这里还有一个小的改动:由于使用了BottomSheetDialogFragment包裹了分享内容,那么截图区域就要使用boundsInRoot。

modifier.onGloballyPositioned {
    onScreenshotBounds(it.boundsInRoot())
}

这是因为boundsInWindow是获取的基于Window的坐标,boundsInRoot是基于root composable的坐标。我们截图用的View是用的LocalView.current,而这个返回的是当前 Composable 所在的 View,也就是承载这个 Compose UI 的 Android View,如:ComposeView

问题4:BottomSheetDialogFragment顶部无圆角

这个问题其实很简单,但是也还是花了很长时间去实现。刚开始按照搜索到方法在onCreate中setStyle 来设置顶部圆角,经过尝试无效;后面在看其他人的解决方案时,找到了根本原因和解决办法。首先需要将BottomSheetDialog的背景色设置为透明色,第二步就是要在根View上将background设置为圆角的shape。

修改BottomSheetDialog背景色为透明的方法:

<style name="BottomSheetDialogTheme" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
    <item name="bottomSheetStyle">@style/ModalBottomSheetDialogStyle</item>
    <item name="android:colorBackground">@color/bottom_sheet_background</item>
</style>

<style name="ModalBottomSheetDialogStyle" parent="Widget.MaterialComponents.BottomSheet.Modal">
    <item name="backgroundTint">@color/transparent</item>
</style>
```
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return BottomSheetDialog(requireContext(), R.style.BottomSheetDialogTheme).apply {
        behavior.skipCollapsed = true
        behavior.state = BottomSheetBehavior.STATE_EXPANDED
    }
}

这里要注意的是,在onCreateView中设置根View背景色为圆角后,嵌套的内容View 不要设置背景色,不然会把圆角覆盖。比如:我用的NestedScrollView作为的根View,ComposeView作为子View,那么我就不能再给子View设置其他颜色的背景色