AndroidEmbeddedExternalSurface
@Composable
fun AndroidEmbeddedExternalSurface(
modifier: Modifier = Modifier,
isOpaque: Boolean = true,
surfaceSize: IntSize = IntSize.Zero,
transform: Matrix? = null,
onInit: AndroidExternalSurfaceScope.() -> Unit,
)
Provides a dedicated drawing Surface embedded directly in the UI hierarchy. Unlike
AndroidExternalSurface, AndroidEmbeddedExternalSurface positions its surface as a regular
element inside the composable hierarchy. This means that graphics composition is handled like any
other UI widget, using the GPU. This can lead to increased power and memory bandwidth usage
compared to AndroidExternalSurface. It is therefore recommended to use AndroidExternalSurface
over AndroidEmbeddedExternalSurface whenever possible.
AndroidEmbeddedExternalSurface can however be useful when interactions with other widgets is
necessary, for instance if the surface needs to be "sandwiched" between two other widgets, or if
it must participate in visual effects driven by a Modifier.graphicsLayer{}.
The Surface provided can be used to present content that's external to Compose, such as a video
stream (from a camera or a media player), OpenGL, Vulkan... The provided Surface can be
rendered into using a thread different from the main thread.
The drawing surface is opaque by default, which can be controlled with the isOpaque parameter.
To start rendering, the caller must first acquire the Surface when it's created. This is
achieved by providing the onInit lambda, which allows the caller to register an appropriate
AndroidExternalSurfaceScope.onSurface callback. The onInit lambda can also be used to
initialize/cache resources needed once a surface is available.
After acquiring a surface, the caller can start rendering into it. Rendering into a surface can be done from any thread.
It is recommended to register the SurfaceScope.onChanged and SurfaceScope.onDestroyed
callbacks to properly handle the lifecycle of the surface and react to dimension changes. You
must ensure that the rendering thread stops interacting with the surface when the
SurfaceScope.onDestroyed callback is invoked.
If a surfaceSize is specified (set to non-IntSize.Zero), the surface will use the specified
size instead of the layout size of this composable. The surface will be stretched at render time
to fit the layout size. This can be used for instance to render at a lower resolution for
performance reasons.
Parameters
| modifier | Modifier to be applied to the AndroidExternalSurface |
| isOpaque | Whether the managed surface should be opaque or transparent. If transparent and isMediaOverlay is false, the surface will be positioned above the parent window. |
| surfaceSize | Sets the surface size independently of the layout size of this AndroidExternalSurface. If set to IntSize.Zero, the surface size will be equal to the AndroidExternalSurface layout size. |
| transform | Sets the transform to apply to the Surface. Some transforms might prevent the content from drawing all the pixels contained within this Composable's bounds. In such situations, make sure to set isOpaque to false. |
| onInit | Lambda invoked on first composition. This lambda can be used to declare a AndroidExternalSurfaceScope.onSurface callback that will be invoked when a surface is available. |
Code Examples
AndroidEmbeddedExternalSurfaceColors
@Composable
fun AndroidEmbeddedExternalSurfaceColors() {
AndroidEmbeddedExternalSurface(modifier = Modifier.fillMaxWidth().height(400.dp)) {
// Resources can be initialized/cached here
// A surface is available, we can start rendering
onSurface { surface, width, height ->
var w = width
var h = height
// Initial draw to avoid a black frame
surface.lockCanvas(Rect(0, 0, w, h)).apply {
drawColor(Color.Yellow.toArgb())
surface.unlockCanvasAndPost(this)
}
// React to surface dimension changes
surface.onChanged { newWidth, newHeight ->
w = newWidth
h = newHeight
}
// Cleanup if needed
surface.onDestroyed {}
// Render loop, automatically cancelled on surface destruction
while (true) {
withFrameNanos { time ->
surface.lockCanvas(Rect(0, 0, w, h)).apply {
val timeMs = time / 1_000_000L
val t = 0.5f + 0.5f * sin(timeMs / 1_000.0f)
drawColor(lerp(Color.Yellow, Color.Red, t).toArgb())
surface.unlockCanvasAndPost(this)
}
}
}
}
}
}
