Apri l'esperienza con la fotocamera

1. Prima di iniziare

Cosa hanno di speciale i pieghevoli?

I pieghevoli sono innovazioni irripetibili. Offrono esperienze uniche e offrono opportunità uniche per soddisfare i tuoi utenti con funzionalità differenziate, come l'UI da tavolo per l'utilizzo a mani libere.

Prerequisiti

  • Conoscenza di base dello sviluppo di app Android
  • Conoscenza di base del framework Hilt Dependency Injection

Cosa creerai

In questo codelab, creerai un'app fotocamera con layout ottimizzati per i dispositivi pieghevoli.

6caebc2739522a1b.png

Inizi con un'app della fotocamera di base che non reagisce alla posizione del dispositivo né sfrutti la fotocamera posteriore migliore per scattare selfie migliori. Aggiorna il codice sorgente per spostare l'anteprima sul display più piccolo quando il dispositivo è aperto e reagire all'impostazione dello smartphone in modalità Da tavolo.

Sebbene l'app Fotocamera sia il caso d'uso più pratico per questa API, entrambe le funzionalità apprese in questo codelab possono essere applicate a qualsiasi app.

Cosa imparerai a fare

  • Come utilizzare Jetpack Window Manager per reagire al cambiamento della postura
  • Come spostare la tua app sullo schermo più piccolo di un pieghevole

Che cosa ti serve

  • Una versione recente di Android Studio
  • Un dispositivo pieghevole o un emulatore pieghevole

2. Configurazione

Ottieni il codice iniziale

  1. Se hai installato Git, puoi semplicemente eseguire il comando seguente. Per verificare se Git è installato, digita git --version nel terminale o nella riga di comando e verifica che venga eseguito correttamente.
git clone https://github.com/android/large-screen-codelabs.git
  1. (Facoltativo) Se non hai Git, puoi fare clic sul pulsante seguente per scaricare tutto il codice di questo codelab:

Apri il primo modulo

  • In Android Studio, apri il primo modulo sotto /step1.

Screenshot di Android Studio che mostra il codice relativo a questo codelab

Se ti viene chiesto di utilizzare l'ultima versione di Gradle, aggiornala.

3. Esegui e osserva

  1. Esegui il codice nel modulo step1.

Come puoi vedere, si tratta di un'app fotocamera semplice. Puoi passare dalla fotocamera anteriore a quella posteriore e viceversa e regolare le proporzioni. Tuttavia, al momento il primo pulsante a sinistra non funziona, ma sarà il punto di accesso per la modalità Selfie posteriore.

a34aca632d75aa09.png

  1. Ora, prova a tenere il dispositivo in una posizione semiaperta, in cui la cerniera non è completamente piatta o chiusa, ma forma un angolo di 90 gradi.

Come puoi vedere, l'app non risponde alle diverse posizioni del dispositivo, quindi il layout non cambia, lasciando la cerniera al centro del mirino.

4. Scopri di più su Jetpack WindowManager

La libreria Jetpack WindowManager aiuta gli sviluppatori di app a creare esperienze ottimizzate per i dispositivi pieghevoli. Contiene la classe FoldingFeature che descrive una piega in un display flessibile o una cerniera tra due pannelli di visualizzazione fisici. La sua API consente di accedere a informazioni importanti relative al dispositivo:

Il corso FoldingFeature contiene informazioni aggiuntive, come occlusionType() o isSeparating(), ma questo codelabe non le analizza in modo approfondito.

A partire dalla versione 1.2.0-beta01, la libreria utilizza WindowAreaController, un'API che consente alla modalità schermo posteriore di spostare la finestra corrente sul display allineata con la fotocamera posteriore, il che è ottimo per scattare selfie con la fotocamera posteriore e per molti altri casi d'uso.

Aggiungi dipendenze

  • Per utilizzare Jetpack WindowManager nella tua app, devi aggiungere le seguenti dipendenze al file build.gradle a livello di modulo:

step1/build.gradle

def work_version = '1.2.0-beta01'
implementation "androidx.window:window:$work_version"
implementation "androidx.window:window-java:$work_version"
implementation "androidx.window:window-core:$work_version"

Ora puoi accedere a entrambi i corsi FoldingFeature e WindowAreaController nella tua app. Puoi usarli per creare la migliore esperienza con la fotocamera pieghevole.

5. Implementare la modalità selfie posteriore

Avvia con la modalità Display posteriore.

L'API che consente questa modalità è l'WindowAreaController, che fornisce informazioni e il comportamento relativo allo spostamento di finestre tra display o aree di visualizzazione su un dispositivo.

Ti consente di eseguire query sull'elenco di WindowAreaInfo con cui è attualmente possibile interagire.

Tramite WindowAreaInfo puoi accedere a WindowAreaSession, un'interfaccia che rappresenta una funzionalità attiva della finestra e lo stato di disponibilità per uno specifico WindowAreaCapability.

  1. Dichiara queste variabili in MainActivity:

step1/MainActivity.kt

private lateinit var windowAreaController: WindowAreaController
private lateinit var displayExecutor: Executor
private var rearDisplaySession: WindowAreaSession? = null
private var rearDisplayWindowAreaInfo: WindowAreaInfo? = null
private var rearDisplayStatus: WindowAreaCapability.Status =
    WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED
private val rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
  1. E inizializzali nel metodo onCreate():

step1/MainActivity.kt

displayExecutor = ContextCompat.getMainExecutor(this)
windowAreaController = WindowAreaController.getOrCreate()

lifecycleScope.launch(Dispatchers.Main) {
  lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
    windowAreaController.windowAreaInfos
      .map{info->info.firstOrNull{it.type==WindowAreaInfo.Type.TYPE_REAR_FACING}}
      .onEach { info -> rearDisplayWindowAreaInfo = info }
      .map{it?.getCapability(rearDisplayOperation)?.status?:  WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED }
      .distinctUntilChanged()
      .collect {
           rearDisplayStatus = it
           updateUI()
      }
  }
}
  1. Ora implementa la funzione updateUI() per attivare o disattivare il pulsante per selfie posteriore, a seconda dello stato attuale:

step1/MainActivity.kt

private fun updateUI() {
    if(rearDisplaySession != null) {
        binding.rearDisplay.isEnabled = true
        // A session is already active, clicking on the button will disable it
    } else {
        when(rearDisplayStatus) {
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> {
                binding.rearDisplay.isEnabled = false
                // RearDisplay Mode is not supported on this device"
            }
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE -> {
                binding.rearDisplay.isEnabled = false
                // RearDisplay Mode is not currently available
            }
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> {
                binding.rearDisplay.isEnabled = true
                // You can enable RearDisplay Mode
            }
            WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> {
                binding.rearDisplay.isEnabled = true
                // You can disable RearDisplay Mode
            }
            else -> {
                binding.rearDisplay.isEnabled = false
                // RearDisplay status is unknown
            }
        }
    }
}

Quest'ultimo passaggio è facoltativo, ma è molto utile per apprendere tutti i possibili stati di un WindowAreaCapability.

  1. Ora implementa la funzione toggleRearDisplayMode, che chiuderà la sessione, se la funzionalità è già attiva, oppure chiama la funzione transferActivityToWindowArea:

step1/CameraViewModel.kt

private fun toggleRearDisplayMode() {
    if(rearDisplayStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) {
        if(rearDisplaySession == null) {
            rearDisplaySession = rearDisplayWindowAreaInfo?.getActiveSession(rearDisplayOperation)
        }
        rearDisplaySession?.close()
    } else {
        rearDisplayWindowAreaInfo?.token?.let { token ->
            windowAreaController.transferActivityToWindowArea(
                token = token,
                activity = this,
                executor = displayExecutor,
                windowAreaSessionCallback = this
            )
        }
    }
}

Nota l'utilizzo di MainActivity come WindowAreaSessionCallback.

L'API Rear Display funziona con un approccio listener: quando richiedi di spostare i contenuti sull'altro display, avvii una sessione che viene restituita tramite il metodo onSessionStarted() del listener. Quando invece vuoi tornare al display interno (e più grande), chiudi la sessione e ricevi una conferma nel metodo onSessionEnded(). Per creare un listener di questo tipo, devi implementare l'interfaccia WindowAreaSessionCallback.

  1. Modifica la dichiarazione MainActivity in modo che implementi l'interfaccia WindowAreaSessionCallback:

step1/MainActivity.kt

class MainActivity : AppCompatActivity(), WindowAreaSessionCallback

Ora implementa i metodi onSessionStarted e onSessionEnded all'interno dell'elemento MainActivity. Questi metodi di callback sono estremamente utili per ricevere notifiche sullo stato della sessione e aggiornare l'app di conseguenza.

Questa volta, però, per semplicità, è sufficiente controllare nel corpo della funzione se ci sono errori e registrare lo stato.

step1/MainActivity.kt

override fun onSessionEnded(t: Throwable?) {
    if(t != null) {
        Log.d("Something was broken: ${t.message}")
    }else{
        Log.d("rear session ended")
    }
}

override fun onSessionStarted(session: WindowAreaSession) {
    Log.d("rear session started [session=$session]")
}
  1. Creare ed eseguire l'app. Se apri il dispositivo e tocchi il pulsante del display posteriore, viene visualizzato un messaggio simile al seguente:

3fa50cce0b0d4b8d.png

  1. Seleziona "Cambia schermo ora" per vedere i contenuti spostati sul display esterno.

6. Implementare la modalità da tavolo

È il momento di rendere la tua app "pieghevole": sposta i contenuti lateralmente o sopra la cerniera del dispositivo in base all'orientamento della piega. Per farlo, accederai all'interno di FoldingStateActor in modo che il codice sia disaccoppiato da Activity per una migliore leggibilità.

La parte principale di questa API è l'interfaccia di WindowInfoTracker, creata con un metodo statico che richiede un Activity:

step1/CameraCodelabDependencies.kt

@Provides
fun provideWindowInfoTracker(activity: Activity) =
        WindowInfoTracker.getOrCreate(activity)

Non è necessario scrivere questo codice poiché è già presente, ma è utile per comprendere come è stato creato l'elemento WindowInfoTracker.

  1. Per rimanere in ascolto di eventuali modifiche delle finestre, ascolta queste modifiche nel metodo onResume() di Activity:

step1/MainActivity.kt

lifecycleScope.launch {
    foldingStateActor.checkFoldingState(
         this@MainActivity, 
         binding.viewFinder
    )
}
  1. Ora apri il file FoldingStateActor, perché è il momento di compilare il metodo checkFoldingState().

Come hai già visto, viene eseguito nella fase RESUMED di Activity e utilizza WindowInfoTracker per ascoltare qualsiasi modifica al layout.

step1/FoldingStateActor.kt

windowInfoTracker.windowLayoutInfo(activity)
      .collect { newLayoutInfo ->
         activeWindowLayoutInfo = newLayoutInfo
         updateLayoutByFoldingState(cameraViewfinder)
      }

Utilizzando l'interfaccia di WindowInfoTracker, puoi chiamare windowLayoutInfo() per raccogliere un elemento Flow di WindowLayoutInfo contenente tutte le informazioni disponibili in DisplayFeature.

L'ultimo passaggio consiste nel reagire a questi cambiamenti e spostare i contenuti di conseguenza. Puoi farlo all'interno del metodo updateLayoutByFoldingState(), un passaggio alla volta.

  1. Assicurati che activityLayoutInfo contenga alcune proprietà DisplayFeature e che almeno una sia una FoldingFeature, altrimenti non devi fare nulla:

step1/FoldingStateActor.kt

val foldingFeature = activeWindowLayoutInfo?.displayFeatures
            ?.firstOrNull { it is FoldingFeature } as FoldingFeature?
            ?: return
  1. Calcola la posizione di piegatura per assicurarti che la posizione del dispositivo influisca sul layout e non rientri nei limiti della gerarchia:

step1/FoldingStateActor.kt

val foldPosition = FoldableUtils.getFeaturePositionInViewRect(
            foldingFeature,
            cameraViewfinder.parent as View
        ) ?: return

Ora, hai la certezza che l'elemento FoldingFeature influisca sul tuo layout, quindi dovrai spostare i tuoi contenuti.

  1. Controlla se il valore di FoldingFeature è HALF_OPEN, altrimenti ripristinerai semplicemente la posizione dei tuoi contenuti. Se è HALF_OPEN, devi eseguire un'altra verifica e agire in modo diverso in base all'orientamento della piegatura:

step1/FoldingStateActor.kt

if (foldingFeature.state == FoldingFeature.State.HALF_OPENED) {
    when (foldingFeature.orientation) {
        FoldingFeature.Orientation.VERTICAL -> {
            cameraViewfinder.moveToRightOf(foldPosition)
        }
        FoldingFeature.Orientation.HORIZONTAL -> {
            cameraViewfinder.moveToTopOf(foldPosition)
        }
    }
} else {
    cameraViewfinder.restore()
}

Se il fold è VERTICAL, sposti i contenuti verso destra, altrimenti li sposti nella posizione top of the fold.

  1. Crea ed esegui l'app, quindi apri il dispositivo e attiva la modalità da tavolo per vedere i contenuti muoversi di conseguenza.

7. Complimenti!

In questo codelab hai scoperto alcune funzionalità uniche dei dispositivi pieghevoli, come la modalità Display posteriore o Da tavolo, e come sbloccarle utilizzando Jetpack WindowManager.

Ora puoi implementare esperienze utente eccezionali per la tua app della fotocamera.

Per approfondire

Reference