Hallo! Willkommen zurück bei unserer Reihe über CameraX und Jetpack Compose. In den vorherigen Beiträgen haben wir die Grundlagen für die Einrichtung einer Kameravorschau behandelt und die Funktion „Zum Fokussieren tippen“ hinzugefügt.
🧱 Teil 1: Eine einfache Kameravorschau mit dem neuen Artifact „camera-compose“ erstellen. Wir haben die Berechtigungsverwaltung und die grundlegende Integration behandelt.
👆 Teil 2: Das Compose-Gesten-System, Grafiken und Koroutinen verwenden, um eine visuelle Tippen-zum-Fokus-Funktion zu implementieren.
🔦 Teil 3 (dieser Beitrag): Compose-UI-Elemente über der Kameravorschau einblenden, um die Nutzerfreundlichkeit zu verbessern.
📂 Teil 4: Adaptive APIs und das Compose-Animations-Framework verwenden, um auf faltbaren Smartphones reibungslose Animationen in den und aus dem Tischmodus zu ermöglichen.
In diesem Beitrag geht es um etwas visuell Ansprechenderes: die Implementierung eines Spotlight-Effekts über der Kameravorschau, wobei als Grundlage für den Effekt die Gesichtserkennung dienen soll. Warum? Keine Ahnung. Aber es sieht auf jeden Fall cool aus 🙂. Und noch wichtiger: Es zeigt, wie wir ganz einfach Sensorkoordinaten in UI-Koordinaten umwandeln können, um sie in Compose zu verwenden.
Gesichtserkennung aktivieren
Zuerst ändern wir das CameraPreviewViewModel, um die Gesichtserkennung zu aktivieren. Wir verwenden die Camera2Interop API, mit der wir über CameraX mit der zugrunde liegenden Camera2 API interagieren können. So können wir Kamerafunktionen nutzen, die von CameraX nicht direkt bereitgestellt werden. Wir müssen die folgenden Änderungen vornehmen:
-
Erstellen Sie einen StateFlow, der die Gesichtsgrenzen als Liste von
Rects enthält. -
Legen Sie die Option für die Erfassungsanfrage
STATISTICS_FACE_DETECT_MODEauf FULL fest, um die Gesichtserkennung zu aktivieren. -
Legen Sie einen
CaptureCallbackfest, um die Gesichtsinformationen aus dem Erfassungsergebnis abzurufen.
class CameraPreviewViewModel : ViewModel() { ... private val _sensorFaceRects = MutableStateFlow(listOf<Rect>()) val sensorFaceRects: StateFlow<List<Rect>> = _sensorFaceRects.asStateFlow() private val cameraPreviewUseCase = Preview.Builder() .apply { Camera2Interop.Extender(this) .setCaptureRequestOption( CaptureRequest.STATISTICS_FACE_DETECT_MODE, CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL ) .setSessionCaptureCallback(object : CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted( session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult ) { super.onCaptureCompleted(session, request, result) result.get(CaptureResult.STATISTICS_FACES) ?.map { face -> face.bounds.toComposeRect() } ?.toList() ?.let { faces -> _sensorFaceRects.update { faces } } } }) } .build().apply { ... }
Nach diesen Änderungen gibt unser Viewmodel eine Liste von Rect-Objekten aus, die die Begrenzungsrahmen der erkannten Gesichter in Sensorkoordinaten darstellen.
Sensorkoordinaten in UI-Koordinaten umwandeln
Die Begrenzungsrahmen der erkannten Gesichter, die wir im letzten Abschnitt gespeichert haben, verwenden Koordinaten im Sensorkoordinatensystem. Um die Begrenzungsrahmen in unserer UI zu zeichnen, müssen wir diese Koordinaten so umwandeln, dass sie im Compose-Koordinatensystem korrekt sind. Dazu müssen wir Folgendes tun:
- Die Sensorkoordinaten in Koordinaten des Vorschaupuffers umwandeln
- Die Koordinaten des Vorschaupuffers in Compose-UI-Koordinaten umwandeln
Diese Transformationen werden mithilfe von Transformationsmatrizen durchgeführt. Jede Transformation hat eine eigene Matrix:
-
Unser
SurfaceRequestenthält eineTransformationInfo-Instanz mit einersensorToBufferTranform-Matrix. -
Unser
CameraXViewfinderhat einen zugehörigenCoordinateTransformer. Sie erinnern sich vielleicht, dass wir diesen Transformer bereits im vorherigen Blogpost verwendet haben, um die Koordinaten für „Zum Fokussieren tippen“ umzuwandeln.
Wir können eine Hilfsmethode erstellen, die die Transformation für uns durchführt:
private fun List<Rect>.transformToUiCoords( transformationInfo: SurfaceRequest.TransformationInfo?, uiToBufferCoordinateTransformer: MutableCoordinateTransformer ): List<Rect> = this.map { sensorRect -> val bufferToUiTransformMatrix = Matrix().apply { setFrom(uiToBufferCoordinateTransformer.transformMatrix) invert() } val sensorToBufferTransformMatrix = Matrix().apply { transformationInfo?.let { setFrom(it.sensorToBufferTransform) } } val bufferRect = sensorToBufferTransformMatrix.map(sensorRect) val uiRect = bufferToUiTransformMatrix.map(bufferRect) uiRect }
- Wir iterieren durch die Liste der erkannten Gesichter und führen für jedes Gesicht die Transformation aus.
-
Die
CoordinateTransformer.transformMatrix, die wir von unseremCameraXViewfindererhalten, wandelt Koordinaten standardmäßig von der UI in Pufferkoordinaten um. In unserem Fall soll die Matrix umgekehrt funktionieren und Pufferkoordinaten in UI-Koordinaten umwandeln. Deshalb verwenden wir die Methodeinvert(), um die Matrix zu invertieren. -
Zuerst wandeln wir das Gesicht mit der
sensorToBufferTransformMatrixvon Sensorkoordinaten in Pufferkoordinaten um und dann mit derbufferToUiTransformMatrixin UI-Koordinaten.
Spotlight-Effekt implementieren
Jetzt aktualisieren wir das zusammensetzbare Element CameraPreviewContent, um den Spotlight-Effekt zu zeichnen. Wir verwenden ein Canvas zusammensetzbares Element, um eine Farbverlaufsmaske über die Vorschau zu zeichnen, sodass die erkannten Gesichter sichtbar sind:
@Composable fun CameraPreviewContent( viewModel: CameraPreviewViewModel, modifier: Modifier = Modifier, lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current ) { val surfaceRequest by viewModel.surfaceRequest.collectAsStateWithLifecycle() val sensorFaceRects by viewModel.sensorFaceRects.collectAsStateWithLifecycle() val transformationInfo by produceState<SurfaceRequest.TransformationInfo?>(null, surfaceRequest) { try { surfaceRequest?.setTransformationInfoListener(Runnable::run) { transformationInfo -> value = transformationInfo } awaitCancellation() } finally { surfaceRequest?.clearTransformationInfoListener() } } val shouldSpotlightFaces by remember { derivedStateOf { sensorFaceRects.isNotEmpty() && transformationInfo != null} } val spotlightColor = Color(0xDDE60991) .. surfaceRequest?.let { request -> val coordinateTransformer = remember { MutableCoordinateTransformer() } CameraXViewfinder( surfaceRequest = request, coordinateTransformer = coordinateTransformer, modifier = .. ) AnimatedVisibility(shouldSpotlightFaces, enter = fadeIn(), exit = fadeOut()) { Canvas(Modifier.fillMaxSize()) { val uiFaceRects = sensorFaceRects.transformToUiCoords( transformationInfo = transformationInfo, uiToBufferCoordinateTransformer = coordinateTransformer ) // Fill the whole space with the color drawRect(spotlightColor) // Then extract each face and make it transparent uiFaceRects.forEach { faceRect -> drawRect( Brush.radialGradient( 0.4f to Color.Black, 1f to Color.Transparent, center = faceRect.center, radius = faceRect.minDimension * 2f, ), blendMode = BlendMode.DstOut ) } } } } }
So funktionierts:
- Wir rufen die Liste der Gesichter aus dem Viewmodel ab.
-
Damit nicht jedes Mal der gesamte Bildschirm neu zusammengesetzt wird, wenn sich die Liste der erkannten Gesichter ändert, verwenden wir
derivedStateOf, um zu verfolgen, ob überhaupt Gesichter erkannt wurden. Dies kann dann mitAnimatedVisibilityverwendet werden, um das farbige Overlay ein- und auszublenden. -
Der
surfaceRequestenthält die Informationen, die wir zum Umwandeln von Sensorkoordinaten in Pufferkoordinaten benötigen, inSurfaceRequest.TransformationInfo. Wir verwenden die FunktionproduceState, um einen Listener in der Oberflächenanfrage einzurichten und diesen Listener zu löschen, wenn das zusammensetzbare Element den Kompositionsbaum verlässt. -
Wir verwenden ein
Canvas, um ein durchscheinendes rosa Rechteck zu zeichnen, das den gesamten Bildschirm abdeckt. -
Wir verschieben das Lesen der Variablen
sensorFaceRectsin denCanvas-Zeichenblock. Dann wandeln wir die Koordinaten in UI-Koordinaten um. - Wir iterieren über die erkannten Gesichter und zeichnen für jedes Gesicht einen radialen Farbverlauf, der das Innere des Gesichtsrechtecks transparent macht.
-
Wir verwenden
BlendMode.DstOut, um sicherzustellen, dass der Farbverlauf aus dem rosa Rechteck ausgeschnitten wird, wodurch der Spotlight-Effekt entsteht.
Hinweis: Wenn Sie die Kamera in DEFAULT_FRONT_CAMERA ändern, wird der Spotlight-Effekt gespiegelt. Dies ist ein bekanntes Problem, das in der Google-Problemverfolgung. dokumentiert ist.
Ergebnis
Mit diesem Code haben wir einen voll funktionsfähigen Spotlight-Effekt, der erkannte Gesichter hervorhebt. Das vollständige Code-Snippet finden Sie hier.
Dieser Effekt ist nur der Anfang. Mit Compose können Sie eine Vielzahl visuell beeindruckender Kameraerlebnisse erstellen. Da wir Sensor- und Pufferkoordinaten in Compose-UI-Koordinaten und zurück umwandeln können, können wir alle Compose-UI-Funktionen nutzen und nahtlos in das zugrunde liegende Kamerasystem integrieren. Mit Animationen, erweiterten UI-Grafiken, einfacher UI-Statusverwaltung und vollständiger Gestensteuerung sind Ihrer Fantasie keine Grenzen gesetzt.
Im letzten Beitrag der Reihe erfahren Sie, wie Sie adaptive APIs und das Compose-Animations-Framework verwenden, um nahtlos zwischen verschiedenen Kamera-UIs auf faltbaren Geräten zu wechseln.
Für die Code-Snippets in diesem Blog gilt die folgende Lizenz:
// Copyright 2024 Google LLC. SPDX-License-Identifier: Apache-2.0
Vielen Dank an Nick Butcher, Alex Vanyo, Trevor McGuire, Don Turner und Lauren Ward für die Überprüfung und das Feedback. Möglich gemacht durch die harte Arbeit von Yasith Vidanaarachch.
Weiterlesen
-
Anleitungen
In diesem Artikel erfahren Sie, wie Sie die waitUntil-Test-API in Compose verwenden, um zu warten, bis bestimmte Bedingungen erfüllt sind.
Jose Alcérreca • Lesezeit: 3 Minuten
-
Anleitungen
Ob Sie Gemini in Android Studio, Gemini CLI, Antigravity oder Drittanbieter-Agents wie Claude Code oder Codex verwenden – unser Ziel ist es, hochwertige Android-Entwicklung überall zu ermöglichen.
Adarsh Fernando, Esteban de la Canal • Lesezeit: 4 Minuten
-
Anleitungen
Da ein übermäßiger Akkuverbrauch für Android-Nutzer ein wichtiges Thema ist, hat Google erhebliche Schritte unternommen, um Entwicklern dabei zu helfen, energieeffizientere Apps zu entwickeln.
Alice Yuan • Lesezeit: 8 Minuten
Auf dem Laufenden bleiben
Lassen Sie sich Woche für Woche die neuesten Informationen zur Android-Entwicklung zusenden.