使用经典
View
时,很容易从视图中获取位图而不显示它。我通过 LayoutInflater
创建视图类,然后,由于它尚未附加到视图,所以我首先测量它。
我有以下扩展函数,可以测量它并在位图上绘制视图:
fun View.toBitmap(width, height): Bitmap {
this.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY),
)
val bitmap = Bitmap.createBitmap(this.measuredWidth, this.measuredHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
this.layout(0, 0, this.measuredWidth, this.measuredHeight)
this.draw(canvas)
return bitmap
}
使用
Composable
s 时,我无法成功从视图导出位图。
我想象了这样的事情:
class MyComposableView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
): AbstractComposeView(context, attrs, defStyleAttr) {
@Composable
override fun Content() {
MyComposable()
}
}
我所做的是使用应用程序上下文实例化一个
MyComposableView
,然后尝试使用扩展函数toBitmap
获取位图。
结果出现以下异常:
java.lang.IllegalStateException: Cannot locate windowRecomposer; View io.myapp.MyComposableView{f66ecdd V.E...... ......I. 0,0-0,0} is not attached to a window
我不明白的是为什么
AbstractComposeView
会抛出异常,但通过充气器获得的视图不会抛出异常。
编辑: 2022 年 4 月 9 日,除了使用经典的 XML 布局之外,似乎没有其他解决方案。
BitmapComposable(
onBitmapped = { bitmap ->
// Do your operation
},
intSize = IntSize(500, 700) // Pixel size for output bitmap
) {
// Composable that you want to convert to a bitmap
// This scope is @Composable
YourComposable()
}
请注意,此可组合项不会在屏幕上显示任何内容!
@Composable
fun BitmapComposable(
onBitmapped: (bitmap: Bitmap) -> Unit = { _ -> },
backgroundColor: Color = Color.Transparent,
dpSize : DpSize,
composable: @Composable () -> Unit
) {
Column(modifier = Modifier
.size(0.dp, 0.dp)
.verticalScroll(
rememberScrollState(), enabled = false
)
.horizontalScroll(
rememberScrollState(), enabled = false
)) {
Box(modifier = Modifier.size(dpSize)) {
AndroidView(factory = {
ComposeView(it).apply {
setContent {
Box(modifier = Modifier.background(backgroundColor).fillMaxSize()) {
composable()
}
}
}
}, modifier = Modifier.fillMaxSize(), update = {
it.run {
doOnLayout {
onBitmapped(drawToBitmap())
}
}
})
}
}
}
@Composable
fun BitmapComposable(
onBitmapped: (bitmap: Bitmap) -> Unit = { _ -> },
backgroundColor: Color = Color.Transparent,
intSize : IntSize, // Pixel size for output bitmap
composable: @Composable () -> Unit
) {
val renderComposableSize = LocalDensity.current.run { intSize.toSize().toDpSize() }
BitmapComposable(onBitmapped,backgroundColor,renderComposableSize,composable)
}
BitmapComposable(
onBitmapped = { bitmap ->
// Do your operation
},
backgroundColor = Color.White,
intSize = IntSize(500, 700) // Pixel size for output bitmap
) {
// Composable that you want to convert to a bitmap
// This scope is @Composable
YourComposable()
}
有一种方法可以通过将可组合内容渲染到不可见窗口中并从那里秘密捕获它来捕获可组合内容。
@Composable
fun InvisibleContent(content: @Composable () -> Unit) {
val context = LocalContext.current
val windowManager = context.getSystemService<WindowManager>()!!
DisposableEffect(key1 = content) {
val composeView = ComposeView(context).apply {
setParentCompositionContext(null)
setContent {
content()
}
setOwners(context.findActivity())
}
windowManager.addView(
/* view = */ composeView,
/* params = */ WindowManager.LayoutParams(
/* w = */ WindowManager.LayoutParams.WRAP_CONTENT,
/* h = */ WindowManager.LayoutParams.WRAP_CONTENT,
/* _type = */ WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
/* _flags = */ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
/* _format = */ PixelFormat.TRANSLUCENT
)
)
onDispose { windowManager.removeView(composeView) }
}
}
private fun View.setOwners(fromActivity: ComponentActivity) {
if (findViewTreeLifecycleOwner() == null) {
setViewTreeLifecycleOwner(fromActivity)
}
if (findViewTreeViewModelStoreOwner() == null) {
setViewTreeViewModelStoreOwner(fromActivity)
}
if (findViewTreeSavedStateRegistryOwner() == null) {
setViewTreeSavedStateRegistryOwner(fromActivity)
}
}
/**
* Traverses through this [Context] and finds [Activity] wrapped inside it.
*/
private fun Context.findActivity(): ComponentActivity {
var context = this
while (context is ContextWrapper) {
if (context is ComponentActivity) return context
context = context.baseContext
}
throw IllegalStateException("Unable to retrieve Activity from the current context")
}
在此示例中,使用 Capturable 来捕获内容,因为它使用 Compose 画布来绘制图片。
@Composable
fun CaptureDemo() {
val captureController = rememberCaptureController()
val uiScope = rememberCoroutineScope()
InvisibleContent {
ContentToCaptureComposable(modifier = Modifier.capturable(captureController))
}
Button(
onClick = {
uiScope.launch {
ticketBitmap = captureController.captureAsync().await()
}
}
) {
Text("Preview Ticket Image")
}
}
在这里,
ContentToCaptureComposable
可组合项的内容不会显示在 UI 上,也不会与相关可组合项一起出现在同一视图的 UI 中。相反,它会秘密地添加到另一个不可见的窗口上。
我已经尝试过这个并且有效。