I want to take screenshot of specific composable function on Jetpack Compose. How can I do this? Please, anyone help me. I want to take screenshot of composable function and share with other applications.
Example of my function:
@Composable
fun PhotoCard() {
Stack() {
Image(imageResource(id = R.drawable.background))
Text(text = "Example")
}
}
How to take screenshot of this function?
Using PixelCopy worked for me:
@RequiresApi(Build.VERSION_CODES.O)
suspend fun Window.drawToBitmap(
config: Bitmap.Config = Bitmap.Config.ARGB_8888,
timeoutInMs: Long = 1000
): Bitmap {
var result = PixelCopy.ERROR_UNKNOWN
val latch = CountDownLatch(1)
val bitmap = Bitmap.createBitmap(decorView.width, decorView.height, config)
PixelCopy.request(this, bitmap, { copyResult ->
result = copyResult
latch.countDown()
}, Handler(Looper.getMainLooper()))
var timeout = false
withContext(Dispatchers.IO) {
runCatching {
timeout = !latch.await(timeoutInMs, TimeUnit.MILLISECONDS)
}
}
if (timeout) error("Failed waiting for PixelCopy")
if (result != PixelCopy.SUCCESS) error("Non success result: $result")
return bitmap
}
Example:
val scope = rememberCoroutineScope()
val context = LocalContext.current as Activity
var bitmap by remember { mutableStateOf<Bitmap?>(null) }
Button(onClick = {
scope.launch {
//wrap in a try catch/block
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
bitmap = context.window.drawToBitmap()
}
}
}) {
Text(text = "Take Screenshot")
}
Box(
modifier = Modifier
.background(Color.Red)
.padding(10.dp)
) {
bitmap?.let {
Image(
bitmap = it.asImageBitmap(),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
)
}
}
You can get position of a composable view inside the root compose view using onGloballyPositioned, and then draw the needed part of the root view into the Bitmap:
val view = LocalView.current
var capturingViewBounds by remember { mutableStateOf<Rect?>(null) }
Button(onClick = {
val bounds = capturingViewBounds ?: return@Button
val image = Bitmap.createBitmap(
bounds.width.roundToInt(), bounds.height.roundToInt(),
Bitmap.Config.ARGB_8888
).applyCanvas {
translate(-bounds.left, -bounds.top)
view.draw(this)
}
}) {
Text("Capture")
}
ViewToCapture(
modifier = Modifier
.onGloballyPositioned {
capturingViewBounds = it.boundsInRoot()
}
)
Note that if you have some view on top of ViewToCapture, like placed with a Box, it'll still be on the image.
p.s. there's a bug which makes Modifier.graphicsLayer effects, offset { IntOffset(...) }(you still can use offset(dp) in this case), scrollable and lazy views position not being displayed correctly on the screenshot. If you've faced it, please star the issue to get more attention.
As @Commonsware mentioned in the comment, and assuming this is not about screenshot testing:
According to official docs you can access the view version of your composable function using LocalView.current, and export that view to a bitmap file like this (the following code goes inside the composable function):
val view = LocalView.current
val context = LocalContext.current
val handler = Handler(Looper.getMainLooper())
handler.postDelayed(Runnable {
val bmp = Bitmap.createBitmap(view.width, view.height,
Bitmap.Config.ARGB_8888).applyCanvas {
view.draw(this)
}
bmp.let {
File(context.filesDir, "screenshot.png")
.writeBitmap(bmp, Bitmap.CompressFormat.PNG, 85)
}
}, 1000)
The writeBitmap method is a simple extension function for File class. Example:
private fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) {
outputStream().use { out ->
bitmap.compress(format, quality, out)
out.flush()
}
}
I was looking for how to take screenshot of a composable in tests and this question appeared as the first in results. So, for future users who want to take/save/compare screenshots in tests or do screenshot testing, I put my answer here (thanks to this).
Ensure you have this dependency along with other Compose dependencies:
debugImplementation("androidx.compose.ui:ui-test-manifest:<version>")
Note: Instead of the above dependency, you can simply add an AndroidManifest.xml file in androidTest directory and add <activity android:name="androidx.activity.ComponentActivity" /> in manifest ➜ application element.
Refer to this answer.
The following is a complete example of taking, saving, reading, and comparing screenshots of a composable function called MyComposableFunction:
class ScreenshotTest {
@get:Rule val composeTestRule = createComposeRule()
@Test fun takeAndSaveScreenshot() {
composeTestRule.setContent { MyComposableFunction() }
val node = composeTestRule.onRoot()
val screenshot = node.captureToImage().asAndroidBitmap()
saveScreenshot("screenshot.png", screenshot)
}
@Test fun readAndCompareScreenshots() {
composeTestRule.setContent { MyComposableFunction() }
val node = composeTestRule.onRoot()
val screenshot = node.captureToImage().asAndroidBitmap()
val context = InstrumentationRegistry.getInstrumentation().targetContext
val path = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val file = File(path, "screenshot.png")
val saved = readScreenshot(file)
println("Are screenshots the same: ${screenshot.sameAs(saved)}")
}
private fun readScreenshot(file: File) = BitmapFactory.decodeFile(file.path)
private fun saveScreenshot(filename: String, screenshot: Bitmap) {
val context = InstrumentationRegistry.getInstrumentation().targetContext
// Saves in /Android/data/your.package.name.test/files/Pictures on external storage
val path = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val file = File(path, filename)
file.outputStream().use { stream ->
screenshot.compress(Bitmap.CompressFormat.PNG, 100, stream)
}
}
}
I've also answered a similar question here.
You can create a test, set the content to that composable and then call composeTestRule.captureToImage(). It returns an ImageBitmap.
Example of usage in a screenshot comparator: https://github.com/android/compose-samples/blob/e6994123804b976083fa937d3f5bf926da4facc5/Rally/app/src/androidTest/java/com/example/compose/rally/ScreenshotComparator.kt
You can create a preview function with @Preview , run the function on phone or emulator and take the screenshot of the component.