可组合为位图而不显示它

问题描述 投票:0回答:2

使用经典

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 布局之外,似乎没有其他解决方案。

android bitmap composable
2个回答
0
投票

您可以在没有 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()
}

1 - 复制此功能:

请注意,此可组合项不会在屏幕上显示任何内容!

@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)
}

2 - 像这样使用:

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()
}

0
投票

有一种方法可以通过将可组合内容渲染到不可见窗口中并从那里秘密捕获它来捕获可组合内容。

创建不可见的可组合项

@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 中。相反,它会秘密地添加到另一个不可见的窗口上。

我已经尝试过这个并且有效。

© www.soinside.com 2019 - 2024. All rights reserved.