AndroidExternalSurface

Functions summary

Unit
@Composable
AndroidExternalSurface(
    modifier: Modifier,
    isOpaque: Boolean,
    surfaceSize: IntSize,
    zOrder: AndroidExternalSurfaceZOrder,
    isSecure: Boolean,
    onInit: AndroidExternalSurfaceScope.() -> Unit
)

Provides a dedicated drawing Surface as a separate layer positioned by default behind the window holding the AndroidExternalSurface composable.

android

Functions

AndroidExternalSurface

@Composable
fun AndroidExternalSurface(
    modifier: Modifier = Modifier,
    isOpaque: Boolean = true,
    surfaceSize: IntSize = IntSize.Zero,
    zOrder: AndroidExternalSurfaceZOrder = AndroidExternalSurfaceZOrder.Behind,
    isSecure: Boolean = false,
    onInit: AndroidExternalSurfaceScope.() -> Unit
): Unit

Provides a dedicated drawing Surface as a separate layer positioned by default behind the window holding the AndroidExternalSurface composable. Because AndroidExternalSurface uses a separate window layer, graphics composition is handled by the system compositor which can bypass the GPU and provide better performance and power usage characteristics compared to AndroidEmbeddedExternalSurface. It is therefore recommended to use AndroidExternalSurface over AndroidEmbeddedExternalSurface whenever possible.

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 z-ordering of the surface can be controlled using the zOrder parameter:

The drawing surface is opaque by default, which can be controlled with the isOpaque parameter. When the surface is transparent, you may need to change the z-order to see something behind the surface.

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.

import androidx.compose.foundation.AndroidExternalSurface
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.dp

AndroidExternalSurface(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.Blue.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.Blue, Color.Green, t).toArgb())
                    surface.unlockCanvasAndPost(this)
                }
            }
        }
    }
}
Parameters
modifier: Modifier = Modifier

Modifier to be applied to the AndroidExternalSurface

isOpaque: Boolean = true

Whether the managed surface should be opaque or transparent.

surfaceSize: IntSize = IntSize.Zero

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.

zOrder: AndroidExternalSurfaceZOrder = AndroidExternalSurfaceZOrder.Behind

Sets the z-order of the surface relative to its parent window.

isSecure: Boolean = false

Control whether the surface view's content should be treated as secure, preventing it from appearing in screenshots or from being viewed on non-secure displays.

onInit: AndroidExternalSurfaceScope.() -> Unit

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.