Esegui la migrazione da Camera1 a CameraX

Se la tua app utilizza la versione originale di Camera ("Camera1"), che è stata ritirata dal momento Android 5.0 (livello API 21), ti consigliamo vivamente di passare a un'API moderna per fotocamera Android. Offerte Android CameraX (una fotocamera Jetpack standardizzata e robusta API) e Camera2 (un'API del framework di basso livello). Per Nella maggior parte dei casi, ti consigliamo di eseguire la migrazione dell'app a CameraX. Di seguito le ragioni:

  • Facilità d'uso: CameraX gestisce i dettagli di basso livello, così puoi: concentrati meno sulla creazione di un'esperienza con la fotocamera da zero per differenziare la tua app.
  • CameraX gestisce la frammentazione al posto tuo: CameraX riduce le attività a lungo termine costi di manutenzione e codice specifico del dispositivo, offrendo esperienze di qualità superiore agli utenti. Per saperne di più, dai un'occhiata alla nostra Migliore compatibilità dei dispositivi con CameraX post del blog.
  • Funzionalità avanzate: CameraX è stata progettata con attenzione per rendere avanzate una funzionalità semplice da incorporare nella tua app. Ad esempio, puoi facilmente e applicare Bokeh, Ritocco viso, HDR (High Dynamic Range) e luminosità in condizioni di scarsa illuminazione Modalità Acquisizione notturna per le tue foto con Estensioni di CameraX.
  • Aggiornabilità: Android rilascia nuove funzionalità e correzioni di bug su CameraX nel corso dell'anno. Eseguendo la migrazione a CameraX, la tua app avrà la versione più recente di Android tecnologia fotocamera a ogni release di CameraX, non solo sulla di release annuali della versione Android.

In questa guida troverai scenari comuni per le applicazioni della fotocamera. Ciascuna uno scenario include un'implementazione di Camera1 e un'implementazione di CameraX per a confronto.

Quando si tratta di migrazione, a volte hai bisogno di una maggiore flessibilità per l'integrazione con un codebase esistente. Tutto il codice CameraX in questa guida ha un CameraController l'implementazione, ideale se vuoi il modo più semplice di utilizzare CameraX, oltre a una CameraProvider implementazione, ideale se hai bisogno di maggiore flessibilità. Per aiutarti a decidere quale quello giusto per te, ecco i vantaggi di ciascuno:

Controller videocamera

Fornitore fotocamera

Richiede un piccolo codice di configurazione Consente un maggiore controllo
Se permetti a CameraX di gestire un numero maggiore di operazioni di configurazione, le funzionalità di tocco per mettere a fuoco e pizzica per lo zoom funzionano automaticamente Poiché lo sviluppatore dell'app gestisce la configurazione, ci sono più opportunità per personalizzare la configurazione, ad esempio abilitando la rotazione dell'immagine di output o impostando il formato dell'immagine di output in ImageAnalysis
La richiesta di PreviewView per l'anteprima della fotocamera consente CameraX per offrire una perfetta integrazione end-to-end, come nel nostro ML Kit integrazione che può mappare le coordinate dei risultati del modello ML (come una faccia riquadri di delimitazione) direttamente sulle coordinate di anteprima La possibilità di utilizzare una "Piattaforma" personalizzata per l'anteprima della fotocamera consente di maggiore flessibilità, ad esempio l'utilizzo del codice "Surface" esistente che potrebbe essere un input per altre parti della tua app

Se non riesci a eseguire la migrazione, contattaci sul Gruppo di discussione di CameraX.

Prima della migrazione

Confrontare l'utilizzo di CameraX con Camera1

Anche se il codice può avere un aspetto diverso, i concetti alla base di Camera1 e CameraX è molto simile. FotocameraX abbrevia le funzionalità più comuni della fotocamera nei casi d'uso, Di conseguenza, molte attività affidate allo sviluppatore in Fotocamera1 gestite automaticamente da CameraX. Esistono quattro UseCase in CameraX, che puoi usare da utilizzare per varie attività della videocamera: Preview, ImageCapture, VideoCapture e ImageAnalysis.

Un esempio di CameraX che gestisce i dettagli di basso livello per gli sviluppatori è la ViewPort condiviso tra UseCase attivi. In questo modo, tutti i componenti UseCase vedranno esattamente gli stessi pixel. In Fotocamera1, devi gestire personalmente questi dettagli e data la variabilità nelle proporzioni dei vari dispositivi sensori e schermi della videocamera, può essere difficile assicurati che l'anteprima corrisponda alle foto e ai video acquisiti.

Per fare un altro esempio, CameraX gestisce automaticamente i callback Lifecycle nella Lifecycle istanza, la superi. Ciò significa che CameraX gestisce le alla videocamera durante l'intero processo Ciclo di vita dell'attività di Android, inclusi i seguenti casi: chiusura della fotocamera quando l'app accede il contesto; rimuovendo l'anteprima della fotocamera quando lo schermo non richiede più la visualizzazione; e mettendo in pausa l'anteprima della fotocamera quando scatta un'altra attività la precedenza in primo piano, ad esempio una videochiamata in arrivo.

Infine, CameraX gestisce la rotazione e il ridimensionamento senza necessità di codice aggiuntivo da parte tua. Nel caso di un Activity con orientamento sbloccato, La configurazione di UseCase viene eseguita ogni volta che il dispositivo viene ruotato, mentre il sistema distrugge e ricrea l'Activity in caso di modifiche di orientamento. Questo comporta UseCases imposta la rotazione target in modo che corrisponda all'orientamento del display del predefinito ogni volta. Scopri di più sulle rotazioni in CameraX.

Prima di entrare nel dettaglio, diamo uno sguardo generale alle UseCase e come si rappresenterebbe un'app Fotocamera1. (I concetti di CameraX sono blu e Fotocamera1 si trovano in verde.

FotocameraX

Configurazione CameraController / CameraProvider
Anteprima Acquisizione di immagini Acquisizione video Analisi delle immagini
Gestisci la superficie di anteprima e impostala sulla fotocamera Imposta PictureCallback e chiama takePicture() sulla fotocamera Gestione della configurazione della videocamera e di MediaRecorder in un ordine specifico Codice di analisi personalizzato basato sulla superficie di anteprima
Codice specifico per dispositivo
Gestione della rotazione e della scalabilità dei dispositivi
Gestione delle sessioni della videocamera (selezione della videocamera, gestione del ciclo di vita)

Fotocamera1

Compatibilità e prestazioni in CameraX

CameraX supporta dispositivi con Android 5.0 (livello API 21) e versioni successive. Questo rappresenta oltre il 98% dei dispositivi Android esistenti. CameraX è progettata per gestire automaticamente le differenze tra i dispositivi, riducendo la necessità di usare codice nella tua app. Inoltre testiamo oltre 150 dispositivi fisici su tutti i dispositivi Android versioni successive alla 5.0 nel nostro CameraX Test Lab. Tu consultare l'elenco completo dispositivi attualmente presenti in Test Lab.

CameraX utilizza un Executor per per guidare lo stack di videocamere. Puoi impostare il proprio esecutore su CameraX se la tua app ha requisiti specifici per l'organizzazione in thread. Se non viene configurato, CameraX crea e utilizza un valore interno predefinito ottimizzato per il Executor. Molte delle API della piattaforma la fotocameraX creata richiede il blocco della comunicazione tra processi (IPC) con che a volte può impiegare centinaia di millisecondi per rispondere. Per questo motivo, CameraX chiama queste API solo dai thread in background, assicurando così che il thread principale non viene bloccato e la UI rimane fluida. Scopri di più sui thread.

Se il mercato di destinazione della tua app include dispositivi di fascia bassa, CameraX ti offre per ridurre i tempi di configurazione limitatore di videocamera. Poiché il di connessione ai componenti hardware può richiedere una quantità non banale soprattutto sui dispositivi di fascia bassa, puoi specificare l'insieme di videocamere e alle esigenze aziendali. CameraX si connette a queste videocamere solo durante la configurazione. Ad esempio, se l'applicazione utilizza solo fotocamere posteriori, può impostare questa configurazione con DEFAULT_BACK_CAMERA, quindi CameraX evita l'inizializzazione della fotocamera anteriore videocamere per ridurre la latenza.

Concetti di sviluppo Android

Questa guida presuppone una conoscenza generale dello sviluppo di Android. Oltre di base, ecco un paio di concetti utili prima di comprenderli per analizzare il codice riportato di seguito:

  • L'opzione View Binding genera una classe di associazione per i file di layout XML, consentendoti di semplificare fare riferimento alle visualizzazioni in Attività, come nei vari snippet di codice riportati di seguito. Ci sono alcuni differenze tra associazione della vista e findViewById() (il modo precedente per fare riferimento alle viste), ma nel codice seguente dovresti essere in grado di sostituisci le righe di associazione della visualizzazione con una chiamata findViewById() simile.
  • Le coroutine asincrone sono una progettazione contemporanea aggiunto in Kotlin 1.3 che può essere utilizzato per gestire i metodi CameraX che restituisce un ListenableFuture. Questo è ancora più semplice con il Jetpack Libreria Concurrent a partire dalla versione 1.1.0. Per aggiungere una coroutine asincrona alla tua app:
    1. Aggiungi implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0") al file Gradle.
    2. Inserisci qualsiasi codice CameraX che restituisca ListenableFuture in un launch bloccare o funzione di sospensione.
    3. Aggiungi un await() alla chiamata di funzione che restituisce ListenableFuture.
    4. Per una comprensione più approfondita di come funzionano le coroutine, consulta la Inizia una coroutine.

Esegui la migrazione di scenari comuni

Questa sezione spiega come migrare scenari comuni da Camera1 a CameraX. Ogni scenario riguarda un'implementazione di Camera1, un CameraProvider di CameraX e un'implementazione CameraController di CameraX.

Selezione di una videocamera

Una delle prime cose che potresti offrire nell'applicazione della fotocamera è un per selezionare videocamere diverse.

Fotocamera1

In Camera1, puoi chiamare Camera.open() senza parametri per aprire la prima fotocamera posteriore oppure puoi passare un ID intero per fotocamera che vuoi aprire. Ecco un esempio di come potrebbe apparire:

// Camera1: select a camera from id.

// Note: opening the camera is a non-trivial task, and it shouldn't be
// called from the main thread, unlike CameraX calls, which can be
// on the main thread since CameraX kicks off background threads
// internally as needed.

private fun safeCameraOpen(id: Int): Boolean {
    return try {
        releaseCameraAndPreview()
        camera = Camera.open(id)
        true
    } catch (e: Exception) {
        Log.e(TAG, "failed to open camera", e)
        false
    }
}

private fun releaseCameraAndPreview() {
    preview?.setCamera(null)
    camera?.release()
    camera = null
}

CameraX: CameraController

In CameraX, la selezione della fotocamera è gestita dalla classe CameraSelector. FotocameraX semplifica il caso comune di utilizzo della videocamera predefinita. Puoi specificare se vuoi la fotocamera anteriore o la fotocamera posteriore predefinita. Inoltre, L'oggetto CameraControl di CameraX ti consente di impostare il livello di zoom per la tua app, quindi se la tua app è in esecuzione su un dispositivo che supporta fotocamere logiche, all'obiettivo corretto.

Ecco il codice CameraX per utilizzare la fotocamera posteriore predefinita con un CameraController:

// CameraX: select a camera with CameraController

var cameraController = LifecycleCameraController(baseContext)
val selector = CameraSelector.Builder()
    .requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
cameraController.cameraSelector = selector

CameraX: CameraProvider

Ecco un esempio di selezione della fotocamera anteriore predefinita con CameraProvider (è possibile utilizzare sia la fotocamera anteriore che quella posteriore con CameraController o CameraProvider):

// CameraX: select a camera with CameraProvider.

// Use await() within a suspend function to get CameraProvider instance.
// For more details on await(), see the "Android development concepts"
// section above.
private suspend fun startCamera() {
    val cameraProvider = ProcessCameraProvider.getInstance(this).await()

    // Set up UseCases (more on UseCases in later scenarios)
    var useCases:Array = ...

    // Set the cameraSelector to use the default front-facing (selfie)
    // camera.
    val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

    try {
        // Unbind UseCases before rebinding.
        cameraProvider.unbindAll()

        // Bind UseCases to camera. This function returns a camera
        // object which can be used to perform operations like zoom,
        // flash, and focus.
        var camera = cameraProvider.bindToLifecycle(
            this, cameraSelector, useCases)

    } catch(exc: Exception) {
        Log.e(TAG, "UseCase binding failed", exc)
    }
})

...

// Call startCamera in the setup flow of your app, such as in onViewCreated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    ...

    lifecycleScope.launch {
        startCamera()
    }
}

Se vuoi controllare quale videocamera selezionare, puoi farlo anche in FotocameraX se utilizzi CameraProvider chiamando getAvailableCameraInfos(), che fornisce un oggetto CameraInfo per controllare determinate proprietà della fotocamera, come isFocusMeteringSupported(). Puoi quindi convertirlo in un CameraSelector per utilizzarlo come in precedenza di esempio con il metodo CameraInfo.getCameraSelector().

Puoi ottenere ulteriori dettagli su ogni videocamera utilizzando il Camera2CameraInfo . Chiama getCameraCharacteristic() con una chiave per i dati della fotocamera che ti interessano. Controlla il CameraCharacteristics per un elenco di tutte le chiavi su cui puoi eseguire query.

Di seguito è riportato un esempio di utilizzo di una funzione checkFocalLength() personalizzata che potresti definisci te stesso:

// CameraX: get a cameraSelector for first camera that matches the criteria
// defined in checkFocalLength().

val cameraInfo = cameraProvider.getAvailableCameraInfos()
    .first { cameraInfo ->
        val focalLengths = Camera2CameraInfo.from(cameraInfo)
            .getCameraCharacteristic(
                CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS
            )
        return checkFocalLength(focalLengths)
    }
val cameraSelector = cameraInfo.getCameraSelector()

Visualizzazione di un'anteprima

La maggior parte delle applicazioni della videocamera deve mostrare il feed della videocamera sullo schermo in alcuni punto di accesso. Con Camera1, devi gestire correttamente i callback del ciclo di vita devi anche determinare la rotazione e il ridimensionamento dell'anteprima.

Inoltre, in Fotocamera 1 devi decidere se utilizzare un TextureView o un SurfaceView come piattaforma di anteprima. Entrambe le opzioni prevedono dei compromessi e, in entrambi i casi, Camera1 richiede che tu gestire correttamente la rotazione e la scalabilità. PreviewView di CameraX, sull'altro ha implementazioni sottostanti sia per TextureView che per SurfaceView. CameraX decide quale sia l'implementazione migliore in base a fattori quali il tipo di dispositivo e la versione di Android su cui è in esecuzione l'app. Se se l'implementazione è compatibile, puoi dichiarare la tua preferenza PreviewView.ImplementationMode L'opzione COMPATIBLE utilizza un TextureView per l'anteprima, mentre Il valore PERFORMANCE utilizza un valore SurfaceView (se possibile).

Fotocamera1

Per mostrare un'anteprima, devi scrivere il tuo corso Preview con un dell'implementazione android.view.SurfaceHolder.Callback che viene utilizzata per passare i dati immagine dall'hardware della videocamera un'applicazione. Prima di poter avviare l'anteprima dell'immagine live, Preview deve essere passata all'oggetto Camera.

// Camera1: set up a camera preview.

class Preview(
        context: Context,
        private val camera: Camera
) : SurfaceView(context), SurfaceHolder.Callback {

    private val holder: SurfaceHolder = holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // The Surface has been created, now tell the camera
        // where to draw the preview.
        camera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: IOException) {
                Log.d(TAG, "error setting camera preview", e)
            }
        }
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // Take care of releasing the Camera preview in your activity.
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int,
                                w: Int, h: Int) {
        // If your preview can change or rotate, take care of those
        // events here. Make sure to stop the preview before resizing
        // or reformatting it.
        if (holder.surface == null) {
            return  // The preview surface does not exist.
        }

        // Stop preview before making changes.
        try {
            camera.stopPreview()
        } catch (e: Exception) {
            // Tried to stop a non-existent preview; nothing to do.
        }

        // Set preview size and make any resize, rotate or
        // reformatting changes here.

        // Start preview with new settings.
        camera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: Exception) {
                Log.d(TAG, "error starting camera preview", e)
            }
        }
    }
}

class CameraActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding
    private var camera: Camera? = null
    private var preview: Preview? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Create an instance of Camera.
        camera = getCameraInstance()

        preview = camera?.let {
            // Create the Preview view.
            Preview(this, it)
        }

        // Set the Preview view as the content of the activity.
        val cameraPreview: FrameLayout = viewBinding.cameraPreview
        cameraPreview.addView(preview)
    }
}

CameraX: CameraController

In CameraX, c'è molto meno da gestire per te, lo sviluppatore. Se utilizzi un CameraController, devi usare anche PreviewView. Ciò significa che Preview UseCase è implicito, il che rende la configurazione molto meno semplice:

// CameraX: set up a camera preview with a CameraController.

class MainActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Create the CameraController and set it on the previewView.
        var cameraController = LifecycleCameraController(baseContext)
        cameraController.bindToLifecycle(this)
        val previewView: PreviewView = viewBinding.cameraPreview
        previewView.controller = cameraController
    }
}

CameraX: CameraProvider

Con CameraProvider di CameraX, non è necessario utilizzare PreviewView, semplifica notevolmente la configurazione dell'anteprima su Fotocamera1. Per dimostrazione ai fini, questo esempio utilizza un PreviewView, ma puoi scrivere una SurfaceProvider per passare a setSurfaceProvider() in caso di problemi più complessi e alle esigenze aziendali.

In questo caso, l'elemento Preview UseCase non è implicito come avviene per CameraController, quindi devi configurarlo:

// CameraX: set up a camera preview with a CameraProvider.

// Use await() within a suspend function to get CameraProvider instance.
// For more details on await(), see the "Android development concepts"
// section above.
private suspend fun startCamera() {
    val cameraProvider = ProcessCameraProvider.getInstance(this).await()

    // Create Preview UseCase.
    val preview = Preview.Builder()
        .build()
        .also {
            it.setSurfaceProvider(
                viewBinding.viewFinder.surfaceProvider
            )
        }

    // Select default back camera.
    val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

    try {
        // Unbind UseCases before rebinding.
        cameraProvider.unbindAll()

        // Bind UseCases to camera. This function returns a camera
        // object which can be used to perform operations like zoom,
        // flash, and focus.
        var camera = cameraProvider.bindToLifecycle(
            this, cameraSelector, useCases)

    } catch(exc: Exception) {
        Log.e(TAG, "UseCase binding failed", exc)
    }
})

...

// Call startCamera() in the setup flow of your app, such as in onViewCreated.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    ...

    lifecycleScope.launch {
        startCamera()
    }
}

Tocca per mettere a fuoco

Quando l'anteprima della fotocamera è sullo schermo, un controllo comune è l'impostazione della messa a fuoco punto quando l'utente tocca l'anteprima.

Fotocamera1

Per implementare il tocco per mettere a fuoco nella Fotocamera1, devi calcolare la messa a fuoco ottimale Area per indicare dove Camera deve tentare di concentrarsi. Questo Area è passati in setFocusAreas(). Inoltre, devi impostare una modalità Niente distrazioni compatibile Camera. L'area di messa a fuoco ha effetto solo se la modalità corrente è FOCUS_MODE_AUTO, FOCUS_MODE_MACRO, FOCUS_MODE_CONTINUOUS_VIDEO o FOCUS_MODE_CONTINUOUS_PICTURE.

Ogni Area è un rettangolo con peso specificato. La ponderazione è un valore compreso tra 1 e 1000 e viene utilizzato per assegnare la priorità all'elemento attivo Areas se sono impostati più elementi. Questo esempio utilizza un solo Area, quindi il valore di ponderazione non è importante. Coordinate di il rettangolo è compreso tra -1000 e 1000. Il punto in alto a sinistra è (-1000, -1000). Il punto in basso a destra è (1000, 1000). La direzione è relativa al sensore orientato, cioè ciò che vede il sensore. La direzione non è influenzata dalla la rotazione o il mirroring di Camera.setDisplayOrientation(), quindi devi convertirà le coordinate dell'evento touch nelle coordinate del sensore.

// Camera1: implement tap-to-focus.

class TapToFocusHandler : Camera.AutoFocusCallback {
    private fun handleFocus(event: MotionEvent) {
        val camera = camera ?: return
        val parameters = try {
            camera.getParameters()
        } catch (e: RuntimeException) {
            return
        }

        // Cancel previous auto-focus function, if one was in progress.
        camera.cancelAutoFocus()

        // Create focus Area.
        val rect = calculateFocusAreaCoordinates(event.x, event.y)
        val weight = 1  // This value's not important since there's only 1 Area.
        val focusArea = Camera.Area(rect, weight)

        // Set the focus parameters.
        parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO)
        parameters.setFocusAreas(listOf(focusArea))

        // Set the parameters back on the camera and initiate auto-focus.
        camera.setParameters(parameters)
        camera.autoFocus(this)
    }

    private fun calculateFocusAreaCoordinates(x: Int, y: Int) {
        // Define the size of the Area to be returned. This value
        // should be optimized for your app.
        val focusAreaSize = 100

        // You must define functions to rotate and scale the x and y values to
        // be values between 0 and 1, where (0, 0) is the upper left-hand side
        // of the preview, and (1, 1) is the lower right-hand side.
        val normalizedX = (rotateAndScaleX(x) - 0.5) * 2000
        val normalizedY = (rotateAndScaleY(y) - 0.5) * 2000

        // Calculate the values for left, top, right, and bottom of the Rect to
        // be returned. If the Rect would extend beyond the allowed values of
        // (-1000, -1000, 1000, 1000), then crop the values to fit inside of
        // that boundary.
        val left = max(normalizedX - (focusAreaSize / 2), -1000)
        val top = max(normalizedY - (focusAreaSize / 2), -1000)
        val right = min(left + focusAreaSize, 1000)
        val bottom = min(top + focusAreaSize, 1000)

        return Rect(left, top, left + focusAreaSize, top + focusAreaSize)
    }

    override fun onAutoFocus(focused: Boolean, camera: Camera) {
        if (!focused) {
            Log.d(TAG, "tap-to-focus failed")
        }
    }
}

CameraX: CameraController

CameraController ascolta gli eventi touch di PreviewView per gestire tocca per mettere a fuoco automaticamente. Puoi attivare e disattivare la funzionalità Tocca per mettere a fuoco con setTapToFocusEnabled(), e verifica il valore con il getter corrispondente isTapToFocusEnabled().

La getTapToFocusState() restituisce un oggetto LiveData per tenere traccia delle modifiche allo stato attivo sul CameraController.

// CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView,
// with handlers you can define for focused, not focused, and failed states.

val tapToFocusStateObserver = Observer { state ->
    when (state) {
        CameraController.TAP_TO_FOCUS_NOT_STARTED ->
            Log.d(TAG, "tap-to-focus init")
        CameraController.TAP_TO_FOCUS_STARTED ->
            Log.d(TAG, "tap-to-focus started")
        CameraController.TAP_TO_FOCUS_FOCUSED ->
            Log.d(TAG, "tap-to-focus finished (focus successful)")
        CameraController.TAP_TO_FOCUS_NOT_FOCUSED ->
            Log.d(TAG, "tap-to-focus finished (focused unsuccessful)")
        CameraController.TAP_TO_FOCUS_FAILED ->
            Log.d(TAG, "tap-to-focus failed")
    }
}

cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver)

CameraX: CameraProvider

Quando usi un CameraProvider, è necessaria una configurazione per mettere a fuoco il tocco funziona. Questo esempio presuppone l'utilizzo di PreviewView. In caso contrario, è necessario adatta la logica da applicare al tuo Surface personalizzato.

Ecco i passaggi da seguire quando utilizzi PreviewView:

  1. Configura un rilevatore di gesti per gestire gli eventi di tocco.
  2. Con l'evento tocco, crea un MeteringPoint utilizzando MeteringPointFactory.createPoint().
  3. Con MeteringPoint, crea un FocusMeteringAction.
  4. Con l'oggetto CameraControl su Camera (restituito da bindToLifecycle()), chiama startFocusAndMetering(), superando FocusMeteringAction.
  5. (Facoltativo) Rispondi al FocusMeteringResult.
  6. Imposta il rilevatore di gesti per rispondere agli eventi tocco in PreviewView.setOnTouchListener().
// CameraX: implement tap-to-focus with CameraProvider.

// Define a gesture detector to respond to tap events and call
// startFocusAndMetering on CameraControl. If you want to use a
// coroutine with await() to check the result of focusing, see the
// "Android development concepts" section above.
val gestureDetector = GestureDetectorCompat(context,
    object : SimpleOnGestureListener() {
        override fun onSingleTapUp(e: MotionEvent): Boolean {
            val previewView = previewView ?: return
            val camera = camera ?: return
            val meteringPointFactory = previewView.meteringPointFactory
            val focusPoint = meteringPointFactory.createPoint(e.x, e.y)
            val meteringAction = FocusMeteringAction
                .Builder(meteringPoint).build()
            lifecycleScope.launch {
                val focusResult = camera.cameraControl
                    .startFocusAndMetering(meteringAction).await()
                if (!result.isFocusSuccessful()) {
                    Log.d(TAG, "tap-to-focus failed")
                }
            }
        }
    }
)

...

// Set the gestureDetector in a touch listener on the PreviewView.
previewView.setOnTouchListener { _, event ->
    // See pinch-to-zooom scenario for scaleGestureDetector definition.
    var didConsume = scaleGestureDetector.onTouchEvent(event)
    if (!scaleGestureDetector.isInProgress) {
        didConsume = gestureDetector.onTouchEvent(event)
    }
    didConsume
}

Pizzica per eseguire lo zoom

Aumentare e diminuire lo zoom di un'anteprima è un'altra manipolazione diretta e comune delle l'anteprima della fotocamera. Con il numero crescente di fotocamere sui dispositivi, gli utenti si aspetti che venga selezionato automaticamente l'obiettivo con la lunghezza focale migliore come risultato dello zoom.

Fotocamera1

Esistono due modi per eseguire lo zoom con Fotocamera1. Il metodo Camera.startSmoothZoom() si anima dal livello di zoom corrente al livello di zoom che passi. La Il metodo Camera.Parameters.setZoom() passa direttamente al livello di zoom superato in. Prima di utilizzare uno di questi servizi, chiama isSmoothZoomSupported() oppure isZoomSupported(), rispettivamente, per garantire che i relativi metodi di zoom necessari sono disponibili sulla tua Fotocamera.

Per implementare Pizzica per eseguire lo zoom, questo esempio utilizza setZoom() perché il tocco nella pagina di anteprima attiva continuamente gli eventi quando il pizzico quindi aggiorna il livello di zoom immediatamente ogni volta. La La classe ZoomTouchListener è definita di seguito e deve essere impostata come callback al listener di tocchi della superficie di anteprima.

// Camera1: implement pinch-to-zoom.

// Define a scale gesture detector to respond to pinch events and call
// setZoom on Camera.Parameters.
val scaleGestureDetector = ScaleGestureDetector(context,
    object : ScaleGestureDetector.OnScaleGestureListener {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val camera = camera ?: return false
            val parameters = try {
                camera.parameters
            } catch (e: RuntimeException) {
                return false
            }

            // In case there is any focus happening, stop it.
            camera.cancelAutoFocus()

            // Set the zoom level on the Camera.Parameters, and set
            // the Parameters back onto the Camera.
            val currentZoom = parameters.zoom
            parameters.setZoom(detector.scaleFactor * currentZoom)
        camera.setParameters(parameters)
            return true
        }
    }
)

// Define a View.OnTouchListener to attach to your preview view.
class ZoomTouchListener : View.OnTouchListener {
    override fun onTouch(v: View, event: MotionEvent): Boolean =
        scaleGestureDetector.onTouchEvent(event)
}

// Set a ZoomTouchListener to handle touch events on your preview view
// if zoom is supported by the current camera.
if (camera.getParameters().isZoomSupported()) {
    view.setOnTouchListener(ZoomTouchListener())
}

CameraX: CameraController

Come per il tocco per mettere a fuoco, CameraController ascolta il tocco di PreviewView per gestire automaticamente lo zoom con due dita. Puoi attivare e disattivare pizzica per eseguire lo zoom con setPinchToZoomEnabled(), e verifica il valore con il getter corrispondente isPinchToZoomEnabled().

La getZoomState() restituisce un oggetto LiveData per il monitoraggio delle modifiche apportate al ZoomState il CameraController.

// CameraX: track the state of pinch-to-zoom over the Lifecycle of
// a PreviewView, logging the linear zoom ratio.

val pinchToZoomStateObserver = Observer { state ->
    val zoomRatio = state.getZoomRatio()
    Log.d(TAG, "ptz-zoom-ratio $zoomRatio")
}

cameraController.getZoomState().observe(this, pinchToZoomStateObserver)

CameraX: CameraProvider

Per utilizzare la funzionalità Pizzica per eseguire lo zoom con CameraProvider, è necessaria una configurazione. Se non utilizzi PreviewView, devi adattare la logica da applicare alle Surface personalizzato.

Ecco i passaggi da seguire quando utilizzi PreviewView:

  1. Configura un rilevatore di gesti della bilancia per gestire gli eventi di pizzicatura.
  2. Recupera ZoomState dall'oggetto Camera.CameraInfo, dove Camera viene restituita quando chiami bindToLifecycle().
  3. Se ZoomState ha un valore zoomRatio, salvalo come zoom attuale rapporto. Se su ZoomState non è presente zoomRatio, usa l'impostazione predefinita della videocamera velocità di zoom (1,0).
  4. Prendi il prodotto del rapporto di zoom corrente con scaleFactor a determinare il nuovo rapporto di zoom e passarlo a CameraControl.setZoomRatio().
  5. Imposta il rilevatore di gesti per rispondere agli eventi tocco in PreviewView.setOnTouchListener().
// CameraX: implement pinch-to-zoom with CameraProvider.

// Define a scale gesture detector to respond to pinch events and call
// setZoomRatio on CameraControl.
val scaleGestureDetector = ScaleGestureDetector(context,
    object : SimpleOnGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val camera = camera ?: return
            val zoomState = camera.cameraInfo.zoomState
            val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f
            camera.cameraControl.setZoomRatio(
                detector.scaleFactor * currentZoomRatio
            )
        }
    }
)

...

// Set the scaleGestureDetector in a touch listener on the PreviewView.
previewView.setOnTouchListener { _, event ->
    var didConsume = scaleGestureDetector.onTouchEvent(event)
    if (!scaleGestureDetector.isInProgress) {
        // See pinch-to-zooom scenario for gestureDetector definition.
        didConsume = gestureDetector.onTouchEvent(event)
    }
    didConsume
}

Scattare una foto

In questa sezione viene spiegato come attivare l'acquisizione di foto, se devi farlo su una della pressione del pulsante di scatto, dopo che è trascorso un timer o per qualsiasi altro evento della tua scegliere.

Fotocamera1

In Fotocamera1, devi innanzitutto definire Camera.PictureCallback per gestire i dati delle immagini quando richiesto. Ecco un esempio semplice PictureCallback per la gestione dei dati immagine JPEG:

// Camera1: define a Camera.PictureCallback to handle JPEG data.

private val picture = Camera.PictureCallback { data, _ ->
    val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run {
        Log.d(TAG,
              "error creating media file, check storage permissions")
        return@PictureCallback
    }

    try {
        val fos = FileOutputStream(pictureFile)
        fos.write(data)
        fos.close()
    } catch (e: FileNotFoundException) {
        Log.d(TAG, "file not found", e)
    } catch (e: IOException) {
        Log.d(TAG, "error accessing file", e)
    }
}

Quindi, ogni volta che vuoi scattare una foto, chiama il metodo takePicture(). sulla tua istanza Camera. Questo metodo takePicture() ha tre diverse per tipi di dati diversi. Il primo parametro riguarda un ShutterCallback (che non è definita in questo esempio). Il secondo parametro è di PictureCallback per gestire i dati non elaborati (non compressi) della fotocamera. Il terzo è quello utilizzato in questo esempio, poiché è un PictureCallback da gestire Dati immagine JPEG.

// Camera1: call takePicture on Camera instance, passing our PictureCallback.

camera?.takePicture(null, null, picture)

CameraX: CameraController

CameraController di CameraX mantiene la semplicità di Camera1 per le immagini acquisisca con l'implementazione di un metodo takePicture() proprio. In questo caso, definisci per configurare una voce MediaStore e scattare una foto da salvare lì.

// CameraX: define a function that uses CameraController to take a photo.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

private fun takePhoto() {
   // Create time stamped name and MediaStore entry.
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
              .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
       if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
       }
   }

   // Create output options object which contains file + metadata.
   val outputOptions = ImageCapture.OutputFileOptions
       .Builder(context.getContentResolver(),
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
       .build()

   // Set up image capture listener, which is triggered after photo has
   // been taken.
   cameraController.takePicture(
       outputOptions,
       ContextCompat.getMainExecutor(this),
       object : ImageCapture.OnImageSavedCallback {
           override fun onError(e: ImageCaptureException) {
               Log.e(TAG, "photo capture failed", e)
           }

           override fun onImageSaved(
               output: ImageCapture.OutputFileResults
           ) {
               val msg = "Photo capture succeeded: ${output.savedUri}"
               Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
               Log.d(TAG, msg)
           }
       }
   )
}

CameraX: CameraProvider

Scattare una foto con CameraProvider funziona quasi allo stesso modo che con CameraController, ma devi prima creare e associare un ImageCapture UseCase per avere un oggetto su cui chiamare takePicture():

// CameraX: create and bind an ImageCapture UseCase.

// Make a reference to the ImageCapture UseCase at a scope that can be accessed
// throughout the camera logic in your app.
private var imageCapture: ImageCapture? = null

...

// Create an ImageCapture instance (can be added with other
// UseCase definitions).
imageCapture = ImageCapture.Builder().build()

...

// Bind UseCases to camera (adding imageCapture along with preview here, but
// preview is not required to use imageCapture). This function returns a camera
// object which can be used to perform operations like zoom, flash, and focus.
var camera = cameraProvider.bindToLifecycle(
    this, cameraSelector, preview, imageCapture)

Quando vuoi scattare una foto, puoi chiamare il numero ImageCapture.takePicture(). Visualizza il codice CameraController in questa sezione per un esempio completo della funzione takePhoto().

// CameraX: define a function that uses CameraController to take a photo.

private fun takePhoto() {
    // Get a stable reference of the modifiable ImageCapture UseCase.
    val imageCapture = imageCapture ?: return

    ...

    // Call takePicture on imageCapture instance.
    imageCapture.takePicture(
        ...
    )
}

Registrazione di un video

Registrare un video è molto più complicato degli scenari esaminati fino ad ora. Ogni parte del processo deve essere impostata correttamente, solitamente in un in un determinato ordine. Inoltre, potresti dover verificare che il video e l'audio siano sincronizzare o gestire altre incoerenze tra i dispositivi.

Come vedrai, CameraX gestisce di nuovo gran parte di questa complessità per te.

Fotocamera1

L'acquisizione video con Fotocamera1 richiede un'attenta gestione di Camera e MediaRecorder e i metodi devono essere chiamato in un determinato ordine. Devi seguire questo ordine per che l'applicazione funzioni correttamente:

  1. Apri la fotocamera.
  2. Preparare e avviare un'anteprima (se la tua app mostra il video che viene registrato, come in genere accade).
  3. Sblocca la fotocamera per utilizzarla da MediaRecorder chiamando il numero Camera.unlock().
  4. Configura la registrazione chiamando questi metodi su MediaRecorder:
    1. Connetti la tua istanza Camera a setCamera(camera).
    2. Chiama il numero setAudioSource(MediaRecorder.AudioSource.CAMCORDER).
    3. Chiama il numero setVideoSource(MediaRecorder.VideoSource.CAMERA).
    4. Chiama il numero setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)) per impostare la qualità. Consulta CamcorderProfile per tutti le opzioni di qualità.
    5. Chiama il numero setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()).
    6. Se la tua app ha un'anteprima del video, chiama setPreviewDisplay(preview?.holder?.surface).
    7. Chiama il numero setOutputFormat(MediaRecorder.OutputFormat.MPEG_4).
    8. Chiama il numero setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT).
    9. Chiama il numero setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT).
    10. Chiama prepare() per finalizzare la configurazione di MediaRecorder.
  5. Per avviare la registrazione, chiama il numero MediaRecorder.start().
  6. Per interrompere la registrazione, richiama questi metodi. Anche in questo caso, segui questo ordine esatto:
    1. Chiama il numero MediaRecorder.stop().
    2. Se vuoi, rimuovi l'attuale configurazione di MediaRecorder chiamando MediaRecorder.reset().
    3. Chiama il numero MediaRecorder.release().
    4. Blocca la videocamera in modo che le sessioni future di MediaRecorder possano utilizzarla entro chiamata al numero Camera.lock().
  7. Per interrompere l'anteprima, chiama Camera.stopPreview().
  8. Infine, per rilasciare Camera in modo che altri processi possano utilizzarlo, richiama Camera.release().

Ecco tutti questi passaggi combinati:

// Camera1: set up a MediaRecorder and a function to start and stop video
// recording.

// Make a reference to the MediaRecorder at a scope that can be accessed
// throughout the camera logic in your app.
private var mediaRecorder: MediaRecorder? = null
private var isRecording = false

...

private fun prepareMediaRecorder(): Boolean {
    mediaRecorder = MediaRecorder()

    // Unlock and set camera to MediaRecorder.
    camera?.unlock()

    mediaRecorder?.run {
        setCamera(camera)

        // Set the audio and video sources.
        setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
        setVideoSource(MediaRecorder.VideoSource.CAMERA)

        // Set a CamcorderProfile (requires API Level 8 or higher).
        setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))

        // Set the output file.
        setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())

        // Set the preview output.
        setPreviewDisplay(preview?.holder?.surface)

        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
        setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)

        // Prepare configured MediaRecorder.
        return try {
            prepare()
            true
        } catch (e: IllegalStateException) {
            Log.d(TAG, "preparing MediaRecorder failed", e)
            releaseMediaRecorder()
            false
        } catch (e: IOException) {
            Log.d(TAG, "setting MediaRecorder file failed", e)
            releaseMediaRecorder()
            false
        }
    }
    return false
}

private fun releaseMediaRecorder() {
    mediaRecorder?.reset()
    mediaRecorder?.release()
    mediaRecorder = null
    camera?.lock()
}

private fun startStopVideo() {
    if (isRecording) {
        // Stop recording and release camera.
        mediaRecorder?.stop()
        releaseMediaRecorder()
        camera?.lock()
        isRecording = false

        // This is a good place to inform user that video recording has stopped.
    } else {
        // Initialize video camera.
        if (prepareVideoRecorder()) {
            // Camera is available and unlocked, MediaRecorder is prepared, now
            // you can start recording.
            mediaRecorder?.start()
            isRecording = true

            // This is a good place to inform the user that recording has
            // started.
        } else {
            // Prepare didn't work, release the camera.
            releaseMediaRecorder()

            // Inform user here.
        }
    }
}

CameraX: CameraController

Con CameraController di CameraX, puoi attivare/disattivare ImageCapture, VideoCapture e ImageAnalysis UseCase in modo indipendente, purché l'elenco di casi d'uso possa essere utilizzato contemporaneamente. I UseCase di ImageCapture e ImageAnalysis sono abilitati per impostazione predefinita e ecco perché non hai dovuto chiamare setEnabledUseCases() per scattare una foto.

Per usare CameraController per la registrazione video, devi prima usare setEnabledUseCases() per consentire l'accesso a VideoCapture UseCase.

// CameraX: Enable VideoCapture UseCase on CameraController.

cameraController.setEnabledUseCases(VIDEO_CAPTURE);

Quando vuoi iniziare a registrare video, puoi chiamare il CameraController.startRecording() personalizzata. Questa funzione consente di salvare il video registrato su un File, come puoi vedere nell'esempio riportato di seguito. Inoltre, devi superare un Executor e un corso che implementa OnVideoSavedCallback per gestire i callback di successo ed errore. Quando la registrazione dovrebbe terminare, chiama CameraController.stopRecording()

Nota: se utilizzi CameraX 1.3.0-alpha02 o versioni successive, è disponibile un'ulteriore Parametro AudioConfig che ti consente di attivare o disattivare la registrazione audio sul tuo video. Per attivare registrazione audio, devi assicurarti di disporre delle autorizzazioni di accesso al microfono. Inoltre, il metodo stopRecording() viene rimosso nella versione 1.3.0-alpha02 e startRecording() restituisce un oggetto Recording che può essere usato per la messa in pausa, il ripristino e l'interruzione della registrazione video.

// CameraX: implement video capture with CameraController.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

// Define a VideoSaveCallback class for handling success and error states.
class VideoSaveCallback : OnVideoSavedCallback {
    override fun onVideoSaved(outputFileResults: OutputFileResults) {
        val msg = "Video capture succeeded: ${outputFileResults.savedUri}"
        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
        Log.d(TAG, msg)
    }

    override fun onError(videoCaptureError: Int, message: String,
                         cause: Throwable?) {
        Log.d(TAG, "error saving video: $message", cause)
    }
}

private fun startStopVideo() {
    if (cameraController.isRecording()) {
        // Stop the current recording session.
        cameraController.stopRecording()
        return
    }

    // Define the File options for saving the video.
    val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
        .format(System.currentTimeMillis())

    val outputFileOptions = OutputFileOptions
        .Builder(File(this.filesDir, name))
        .build()

    // Call startRecording on the CameraController.
    cameraController.startRecording(
        outputFileOptions,
        ContextCompat.getMainExecutor(this),
        VideoSaveCallback()
    )
}

CameraX: CameraProvider

Se utilizzi un CameraProvider, devi creare un VideoCapture UseCase e trasmettiamo un oggetto Recorder. Su Recorder.Builder, puoi impostare la qualità video e, facoltativamente, una FallbackStrategy, che gestisce i casi in cui un dispositivo non sia in grado di soddisfare le specifiche di qualità desiderate. Poi associare l'istanza VideoCapture a CameraProvider con l'altra UseCase sec.

// CameraX: create and bind a VideoCapture UseCase with CameraProvider.

// Make a reference to the VideoCapture UseCase and Recording at a
// scope that can be accessed throughout the camera logic in your app.
private lateinit var videoCapture: VideoCapture
private var recording: Recording? = null

...

// Create a Recorder instance to set on a VideoCapture instance (can be
// added with other UseCase definitions).
val recorder = Recorder.Builder()
    .setQualitySelector(QualitySelector.from(Quality.FHD))
    .build()
videoCapture = VideoCapture.withOutput(recorder)

...

// Bind UseCases to camera (adding videoCapture along with preview here, but
// preview is not required to use videoCapture). This function returns a camera
// object which can be used to perform operations like zoom, flash, and focus.
var camera = cameraProvider.bindToLifecycle(
    this, cameraSelector, preview, videoCapture)

A questo punto, è possibile accedere a Recorder sul videoCapture.output proprietà. L'Recorder può avviare registrazioni video salvate in un File, ParcelFileDescriptor o MediaStore. In questo esempio viene utilizzato MediaStore.

Su Recorder, sono disponibili diversi metodi per effettuare una chiamata per prepararlo. Chiama prepareRecording() per impostare le opzioni di output MediaStore. Se la tua app ha per usare il microfono del dispositivo, chiama anche withAudioEnabled(). Quindi, chiama start() per iniziare a registrare, passando in un contesto e una Listener di eventi Consumer<VideoRecordEvent> per gestire gli eventi di registrazione video. Se riuscito, il valore Recording restituito può essere utilizzato per mettere in pausa, riprendere o interrompere registrazione in tempo reale.

// CameraX: implement video capture with CameraProvider.

private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

private fun startStopVideo() {
   val videoCapture = this.videoCapture ?: return

   if (recording != null) {
       // Stop the current recording session.
       recording.stop()
       recording = null
       return
   }

   // Create and start a new recording session.
   val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
       .format(System.currentTimeMillis())
   val contentValues = ContentValues().apply {
       put(MediaStore.MediaColumns.DISPLAY_NAME, name)
       put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
       if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
           put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video")
       }
   }

   val mediaStoreOutputOptions = MediaStoreOutputOptions
       .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
       .setContentValues(contentValues)
       .build()

   recording = videoCapture.output
       .prepareRecording(this, mediaStoreOutputOptions)
       .withAudioEnabled()
       .start(ContextCompat.getMainExecutor(this)) { recordEvent ->
           when(recordEvent) {
               is VideoRecordEvent.Start -> {
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.stop_capture)
                       isEnabled = true
                   }
               }
               is VideoRecordEvent.Finalize -> {
                   if (!recordEvent.hasError()) {
                       val msg = "Video capture succeeded: " +
                           "${recordEvent.outputResults.outputUri}"
                       Toast.makeText(
                           baseContext, msg, Toast.LENGTH_SHORT
                       ).show()
                       Log.d(TAG, msg)
                   } else {
                       recording?.close()
                       recording = null
                       Log.e(TAG, "video capture ends with error",
                             recordEvent.error)
                   }
                   viewBinding.videoCaptureButton.apply {
                       text = getString(R.string.start_capture)
                       isEnabled = true
                   }
               }
           }
       }
}

Risorse aggiuntive

Abbiamo diverse app CameraX complete nel nostro Repository GitHub di esempi di videocamere. Questi esempi mostrano come gli scenari in questa guida si inseriscono in una panoramica App per Android.

Se desideri ulteriore assistenza per la migrazione a CameraX o hai domande in merito alla suite di API Android Camera, contattaci sul Discussione su CameraX Gruppo.