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.
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
- 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
- (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
.
Se ti viene chiesto di utilizzare l'ultima versione di Gradle, aggiornala.
3. Esegui e osserva
- 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.
- 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:
state()
restituisceFLAT
se la cerniera è aperta a 180 gradi oHALF_OPENED
in caso contrario.orientation()
restituisceFoldingFeature.Orientation.HORIZONTAL
se la larghezza diFoldingFeature
è maggiore dell'altezza; altrimenti restituisceFoldingFeature.Orientation.VERTICAL
.bounds()
fornisce i limiti diFoldingFeature
in un formatoRect
.
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.
- 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
- 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()
}
}
}
- 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.
- Ora implementa la funzione
toggleRearDisplayMode
, che chiuderà la sessione, se la funzionalità è già attiva, oppure chiama la funzionetransferActivityToWindowArea
:
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
.
- Modifica la dichiarazione
MainActivity
in modo che implementi l'interfacciaWindowAreaSessionCallback
:
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]")
}
- Creare ed eseguire l'app. Se apri il dispositivo e tocchi il pulsante del display posteriore, viene visualizzato un messaggio simile al seguente:
- 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
.
- Per rimanere in ascolto di eventuali modifiche delle finestre, ascolta queste modifiche nel metodo
onResume()
diActivity
:
step1/MainActivity.kt
lifecycleScope.launch {
foldingStateActor.checkFoldingState(
this@MainActivity,
binding.viewFinder
)
}
- Ora apri il file
FoldingStateActor
, perché è il momento di compilare il metodocheckFoldingState()
.
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.
- Assicurati che
activityLayoutInfo
contenga alcune proprietàDisplayFeature
e che almeno una sia unaFoldingFeature
, altrimenti non devi fare nulla:
step1/FoldingStateActor.kt
val foldingFeature = activeWindowLayoutInfo?.displayFeatures
?.firstOrNull { it is FoldingFeature } as FoldingFeature?
?: return
- 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.
- 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.
- 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.