I'm trying to save a screenshot of my current Activity, but not the whole view, just part of it. In my case: My activity
At the attached image, i want to save a bitmap constructed from views B,C & D.
B is a linear layout, C is a pure bitmap, and D is a relative layout.
To my understanding, one way to go is creating a canvas, add all 'elements' into it and finally have the custom bitmap desired.
I'm having trouble with the following code:
iViewBHeight= viewB.getHeight();
// Prepare empty bitmap as output
Bitmap result = Bitmap.createBitmap(bitmapC.getWidth(),bitmapC.getHeight() + iViewBHeight, Config.ARGB_8888);
// Flush source image into canvas
Canvas canvas = new Canvas(result);
// Draw bitmap C to canvas 'under' view B
canvas.drawBitmap(bitmapC, 0, iViewBHeight, null);
// Draw view B to canvas
viewB.setDrawingCacheEnabled(true);
viewB.buildDrawingCache(true);
canvas.drawBitmap(Bitmap.createBitmap(viewB.getDrawingCache()), 0, 0, null);
viewB.setDrawingCacheEnabled(false);
// Desired bitmap is at 'result'
The result is that bitmap C is drawn Ok, but view B is too large and extends the bitmap. I didn't try adding view D..
Can anyone help me? Maybe there are better ways achieving my goal? Thanks!
View
's drawing cache has been deprecated for a while now, and it was never really a good idea for stuff like this anyway, even though it seemed to be what all of the available examples demonstrated (including mine here).
Messing with the cache is kinda silly to begin with because any View
that works with that approach is also able to be drawn directly to our own Canvas
and Bitmap
, so forcing the View
to do it internally too is redundant and wasteful.
We can further simplify things by allowing the desired image bounds to be defined by the set of View
s that it contains, rather than doing tedious arithmetic for specific setups. Basing the bounds calculations on the Window
's decor View
makes things even easier, and will allow us to capture pretty much any part of an Activity
, even if we don't own all of the content; e.g., like when the ActionBar
is provided automagically.
import android.app.Activity
import android.graphics.Bitmap
import android.graphics.Rect
import android.view.View
import androidx.core.graphics.applyCanvas
import androidx.core.graphics.withTranslation
fun Activity.captureToBitmap(
vararg boundedViews: View,
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Bitmap {
val bounds = Rect()
val viewBounds = Rect()
val viewLocation = IntArray(2)
boundedViews.forEach { view ->
view.getLocationInWindow(viewLocation)
viewBounds.set(0, 0, view.width, view.height)
viewBounds.offset(viewLocation[0], viewLocation[1])
bounds.union(viewBounds)
}
return drawToBitmap(bounds, config)
}
fun Activity.drawToBitmap(
bounds: Rect,
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Bitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), config)
.applyCanvas {
val x = -bounds.left.toFloat()
val y = -bounds.top.toFloat()
withTranslation(x, y, window.decorView::draw)
}
I'm not sure of the exact setup in the question's layout, but if B
contains C
and D
, then calling captureToBitmap(viewB)
is sufficient. Otherwise, there's really no harm in listing everything to make sure:
val bitmap = captureToBitmap(viewB, viewC, viewD)
Of course, you should do this on a separate thread or coroutine, but be careful not to modify any View
s while off the main thread.
If you need a solution in Compose, there's an official example on the Graphics modifiers page.