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 camera-compose-Artefakt erstellen. Wir haben die Berechtigungsverwaltung und die grundlegende Integration behandelt.
👆 Teil 2:Implementieren einer visuellen Tippen-zum-Fokussieren-Funktion mit dem Compose-Gesten-System, Grafiken und Coroutinen.
🔦 Teil 3 (dieser Beitrag): Wir sehen uns an, wie Sie Compose-UI-Elemente über die Kameravorschau legen können, um die Nutzerfreundlichkeit zu verbessern.
📂 Teil 4: Adaptive APIs und das Compose-Animationsframework verwenden, um auf faltbaren Smartphones reibungslose Animationen in den und aus dem Modus „Auf dem Tisch“ zu ermöglichen.
In diesem Beitrag geht es um etwas Visuell Ansprechenderes: Wir implementieren einen Spotlight-Effekt über unserer Kameravorschau und verwenden die Gesichtserkennung als Grundlage für den Effekt. 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
Ändern wir zuerst 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 nicht direkt von CameraX bereitgestellt werden. Wir müssen die folgenden Änderungen vornehmen:
- Erstellen Sie einen StateFlow, der die Gesichtsgrenzen als Liste von
Rects enthält. - Setzen Sie die Option für die Erfassungsanfrage
STATISTICS_FACE_DETECT_MODEauf FULL, um die Gesichtserkennung zu aktivieren. - Legen Sie ein
CaptureCallbackfest, um die Gesichtsinformationen aus dem Erfassungsresultat 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 Ansichtsmodell jetzt eine Liste von Rect-Objekten aus, die die Begrenzungsrahmen der erkannten Gesichter in Sensorkoordinaten darstellen.
Sensorkoordinaten in Koordinaten der Benutzeroberfläche umwandeln
Die Begrenzungsrahmen der erkannten Gesichter, die wir im letzten Abschnitt gespeichert haben, verwenden Koordinaten im Sensorkoordinatensystem. Damit die Begrenzungsrahmen in unserer Benutzeroberfläche gezeichnet werden können, müssen wir diese Koordinaten so transformieren, dass sie im Compose-Koordinatensystem korrekt sind. Wir müssen:
- Sensorkoordinaten in Koordinaten des Vorschaupuffers umwandeln
- Koordinaten des Vorschau-Puffers in Compose-UI-Koordinaten umwandeln
Diese Transformationen werden mithilfe von Transformationsmatrizen durchgeführt. Jede der Transformationen hat eine eigene Matrix:
- Unsere
SurfaceRequestenthält eineTransformationInfo-Instanz, die einesensorToBufferTranform-Matrix enthält. - Unser
CameraXViewfinderhat eine zugehörigeCoordinateTransformer. Sie erinnern sich vielleicht, dass wir diesen Transformer bereits im vorherigen Blogpost verwendet haben, um die Koordinaten für das Tippen zum Fokussieren zu transformieren.
Wir können eine Hilfsmethode erstellen, die die Transformation für uns ausfü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 unsererCameraXViewfindererhalten, transformiert Koordinaten standardmäßig von der UI in Pufferkoordinaten. In unserem Fall soll die Matrix umgekehrt funktionieren und Pufferkoordinaten in UI-Koordinaten umwandeln. Daher verwenden wir dieinvert()-Methode, um die Matrix zu invertieren. - Zuerst transformieren wir das Gesicht mithilfe von
sensorToBufferTransformMatrixvon Sensorkoordinaten in Pufferkoordinaten und dann mithilfe vonbufferToUiTransformMatrixvon Pufferkoordinaten in UI-Koordinaten.
Spotlight-Effekt implementieren
Aktualisieren wir nun die CameraPreviewContent-Composable, um den Spotlight-Effekt zu zeichnen. Wir verwenden eine Canvas-Composable, um eine Farbverlaufsmaske über die Vorschau zu zeichnen und die erkannten Gesichter sichtbar zu machen:
@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 Ansichtsmodell ab.
- Damit wir nicht jedes Mal den gesamten Bildschirm neu rendern, wenn sich die Liste der erkannten Gesichter ändert, verwenden wir
derivedStateOf, um zu verfolgen, ob überhaupt Gesichter erkannt werden. Diese kann dann mitAnimatedVisibilityverwendet werden, um das farbige Overlay ein- und auszublenden. - Die
surfaceRequestenthält die Informationen, die wir benötigen, um Sensorkoordinaten in Pufferkoordinaten in derSurfaceRequest.TransformationInfozu transformieren. Wir verwenden dieproduceState-Funktion, um einen Listener in der Oberflächenanfrage einzurichten, und löschen diesen Listener, wenn die komponierbare Funktion den Kompositionsbaum verlässt. - Wir verwenden ein
Canvas, um ein durchscheinendes rosa Rechteck zu zeichnen, das das gesamte Display abdeckt. - Das Lesen der
sensorFaceRects-Variablen wird aufgeschoben, bis wir uns imCanvas-Block befinden. Anschließend werden die Koordinaten in UI-Koordinaten umgewandelt. - Wir durchlaufen 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 herausgeschnitten wird, wodurch der Spotlight-Effekt entsteht.
Hinweis : Wenn Sie die Kamera auf DEFAULT_FRONT_CAMERA ändern, wird das Spotlight gespiegelt. Dieses Problem ist bekannt und wird im Google Issue Tracker erfasst.
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 erst der Anfang. Mit Compose können Sie eine Vielzahl von visuell beeindruckenden Kamerafunktionen erstellen. Da wir Sensor- und Pufferkoordinaten in Compose-UI-Koordinaten und zurück transformieren können, können wir alle Compose-UI-Funktionen nutzen und sie nahtlos in das zugrunde liegende Kamerasystem einbinden. Mit Animationen, erweiterten UI-Grafiken, einfacher Verwaltung des UI-Status und vollständiger Gestensteuerung sind Ihrer Kreativität keine Grenzen gesetzt.
Im letzten Beitrag der Reihe erfahren Sie, wie Sie adaptive APIs und das Compose-Animationsframework verwenden, um nahtlos zwischen verschiedenen Kamera-UIs auf faltbaren Geräten zu wechseln. Mehr dazu demnächst!
Die Code-Snippets in diesem Blog haben 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 das Überprüfen und Bereitstellen von Feedback. Möglich gemacht durch die harte Arbeit von Yasith Vidanaarachchi.
-
AnleitungenIn diesem Artikel erfahren Sie, wie Sie die Test-API „waitUntil“ in Compose verwenden, um zu warten, bis bestimmte Bedingungen erfüllt sind.
Jose Alcérreca • Lesezeit: 3 Minuten -
AnleitungenDie App-Leistung wird oft mit einer flüssigen Benutzeroberfläche und schnellen Startzeiten gleichgesetzt. Der Arbeitsspeicher ist jedoch die stille Grundlage, auf der diese sichtbaren Messwerte basieren. Es ist kein Geheimnis, dass der Gerätespeicher wichtiger denn je ist.
Alice Yuan, Ajesh Pai, Fung Lam • Lesezeit: 10 Minuten -
AnleitungenWir freuen uns, heute ein neues von Google ausgestelltes Zertifikat für bestätigte E‑Mail-Adressen anzukündigen, das Entwickler jetzt direkt über die Credential Manager Digital Credential API von Android abrufen können.
Niharika Arora, Jean-Pierre Pralle • Lesezeit: 3 Minuten
Lassen Sie sich Woche für Woche die neuesten Informationen zur Android-Entwicklung zusenden.