1. Avant de commencer
Pourquoi choisir les appareils pliables ?
Les appareils pliables constituent des innovations qu'on ne voit qu'une fois par génération. Ils offrent des expériences exceptionnelles et des occasions uniques d'enchanter vos utilisateurs grâce à des fonctionnalités différenciées comme l'interface utilisateur à plat pour une utilisation mains libres.
Conditions préalables
- Connaissances de base sur le développement des applications Android
- Connaissances de base sur le framework d'injection de dépendances avec Hilt
Objectifs de l'atelier
Dans cet atelier de programmation, vous allez créer une application d'appareil photo dont les mises en page sont optimisées pour les appareils pliables.
Vous commencez avec une application d'appareil photo basique qui ne réagit pas aux positions de l'appareil ou qui n'exploite pas la caméra arrière améliorée pour prendre des selfies de meilleure qualité. Vous modifiez le code source de façon à déplacer l'aperçu vers le plus petit écran quand l'appareil est déplié et à obtenir une réaction quand le téléphone est défini en mode sur table.
Bien que l'application d'appareil photo soit le cas d'utilisation le plus pratique pour cette API, les deux fonctionnalités que vous allez apprendre dans cet atelier de programmation peuvent être appliquées à n'importe quelle application.
Points abordés
- Utiliser Jetpack WindowManager pour obtenir une réaction aux changements de position
- Déplacer votre application vers l'écran le plus petit d'un appareil pliable
Ce dont vous aurez besoin
- Une version récente d'Android Studio
- Un appareil pliable ou un émulateur d'appareil pliable
2. Configuration
Obtenir le code de démarrage
- Si Git est installé, vous pouvez simplement exécuter la commande ci-dessous. Pour vérifier si Git est installé, saisissez
git --version
dans le terminal ou la ligne de commande, et vérifiez qu'il s'exécute correctement.
git clone https://github.com/android/large-screen-codelabs.git
- Facultatif : Si vous n'avez pas accès à Git, cliquez sur le bouton ci-dessous pour télécharger l'ensemble du code de cet atelier de programmation : .
Ouvrir le premier module
- Dans Android Studio, ouvrez le premier module sous
/step1
.
Si vous êtes invité à utiliser la dernière version de Gradle, continuez et mettez-la à jour.
3. Exécuter et observer
- Exécutez le code sur le module
step1
.
Comme vous pouvez le voir, il s'agit d'une application d'appareil photo simple. Vous pouvez alterner caméra frontale et caméra arrière, et ajuster le format. Toutefois, le premier bouton en partant de la gauche n'a aucun effet, mais il va être le point d'entrée du mode Selfie avec la caméra arrière.
- Placez maintenant l'appareil dans une position semi-ouverte, dans laquelle la charnière n'est pas complètement à plat ou fermée, mais forme un angle de 90 degrés.
Comme vous pouvez le voir, l'application ne répond pas aux différentes positions de l'appareil et la mise en page ne change donc pas. La charnière reste ainsi au milieu du viseur.
4. Découvrir Jetpack WindowManager
La bibliothèque Jetpack WindowManager aide les développeurs d'applications à créer des expériences optimisées pour les appareils pliables. Elle contient la classe FoldingFeature
, qui décrit le pli d'un écran flexible ou la charnière entre deux écrans physiquement distincts. Son API permet d'accéder à des informations importantes concernant l'appareil :
state()
renvoieFLAT
si la charnière est ouverte à 180 degrés, etHALF_OPENED
dans le cas contraire.orientation()
renvoieFoldingFeature.Orientation.HORIZONTAL
si la largeur de la propriétéFoldingFeature
est supérieure à la hauteur, etFoldingFeature.Orientation.VERTICAL
dans le cas contraire.bounds()
indique les limites de la propriétéFoldingFeature
au formatRect
.
La classe FoldingFeature
contient des informations supplémentaires, comme occlusionType()
ou isSeparating()
, mais nous ne les étudierons pas en détail dans cet atelier de programmation.
À partir de la version 1.2.0-beta01, la bibliothèque utilise l'API WindowAreaController
, qui active le mode d'affichage arrière afin de déplacer la fenêtre actuelle vers l'écran aligné avec la caméra arrière, ce qui est idéal pour prendre des selfies avec la caméra arrière et pour de nombreux autres cas d'utilisation !
Ajouter des dépendances
- Pour utiliser Jetpack WindowManager dans votre application, vous devez ajouter les dépendances suivantes au fichier
build.gradle
au niveau du module :
step1/build.gradle
def work_version = '1.2.0'
implementation "androidx.window:window:$work_version"
implementation "androidx.window:window-java:$work_version"
implementation "androidx.window:window-core:$work_version"
Vous pouvez maintenant accéder aux classes FoldingFeature
et WindowAreaController
dans votre application. Elles vous permettent de créer une expérience photo exceptionnelle sur les appareils pliables !
5. Implémenter le mode Selfie avec la caméra arrière
Commencez par le mode d'affichage arrière.
L'API optimisant ce mode est le WindowAreaController
, qui fournit les informations et le comportement liés au déplacement des fenêtres entre les écrans ou les zones d'affichage d'un appareil.
Elle vous permet d'interroger la liste de WindowAreaInfo
avec lesquelles vous pouvez actuellement interagir.
À l'aide des WindowAreaInfo
, vous pouvez accéder à la WindowAreaSession
, une interface qui représente une fonctionnalité de fenêtre active et l'état de disponibilité pour une WindowAreaCapability.
spécifique.
- Déclarez ces variables dans la propriété
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
- Initialisez-les ensuite dans la méthode
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()
}
}
}
- Implémentez maintenant la fonction
updateUI()
pour activer ou désactiver le bouton de selfie avec la caméra arrière, en fonction de l'état actuel :
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
}
}
}
}
Cette dernière étape est facultative, mais elle est très utile pour connaître tous les états possibles d'une WindowAreaCapability.
.
- Implémentez maintenant la fonction
toggleRearDisplayMode
, qui fermera la session si la capacité est déjà active, ou appelez la fonctiontransferActivityToWindowArea
:
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
)
}
}
}
Notez l'utilisation de la MainActivity
en tant que WindowAreaSessionCallback
.
L'API Rear Display fonctionne selon une approche d'écouteur : lorsque vous demandez à déplacer le contenu vers l'autre écran, vous démarrez une session qui est renvoyée via la méthode onSessionStarted()
de l'écouteur. Quand vous souhaitez revenir à l'écran intérieur (plus grand), vous fermez la session et vous obtenez une confirmation dans la méthode onSessionEnded()
. Pour créer un tel écouteur, vous devez implémenter l'interface WindowAreaSessionCallback
.
- Modifiez la déclaration
MainActivity
de sorte qu'elle implémente l'interfaceWindowAreaSessionCallback
:
step1/MainActivity.kt
class MainActivity : AppCompatActivity(), WindowAreaSessionCallback
Implémentez maintenant les méthodes onSessionStarted
et onSessionEnded
dans MainActivity
. Ces méthodes de rappel sont extrêmement utiles pour recevoir une notification de l'état de la session et mettre à jour l'application en conséquence.
Toutefois, cette fois, pour plus de simplicité, vérifiez simplement dans le corps de la fonction s'il existe des erreurs et consignez l'état.
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]")
}
- Compilez et exécutez l'application. Si vous dépliez ensuite votre appareil et appuyez sur le bouton d'affichage arrière, un message semblable à celui-ci s'affiche :
- Sélectionnez Changer d'écran maintenant pour déplacer votre contenu vers l'écran extérieur.
6. Implémenter le mode sur table
Il est maintenant temps de rendre votre application pliable : déplacez votre contenu sur le côté ou au-dessus de la charnière de l'appareil en fonction de l'orientation du pli. Pour ce faire, vous allez écrire du code dans l'objet FoldingStateActor
pour que votre code soit distinct de l'objet Activity
dans un souci de lisibilité.
L'élément principal de cette API est l'interface WindowInfoTracker
, qui est créée à l'aide d'une méthode statique nécessitant un objet Activity
:
step1/CameraCodelabDependencies.kt
@Provides
fun provideWindowInfoTracker(activity: Activity) =
WindowInfoTracker.getOrCreate(activity)
Vous n'avez pas besoin d'écrire ce code puisqu'il est déjà présent, mais il est utile de comprendre la conception de l'interface WindowInfoTracker
.
- Écoutez les modifications des fenêtres dans la méthode
onResume()
de votreActivity
:
step1/MainActivity.kt
lifecycleScope.launch {
foldingStateActor.checkFoldingState(
this@MainActivity,
binding.viewFinder
)
}
- Ouvrez maintenant le fichier
FoldingStateActor
puisqu'il est temps de remplir la méthodecheckFoldingState()
.
Comme vous l'avez déjà vu, elle s'exécute lors de la phase RESUMED
de votre Activity
et elle utilise l'interface WindowInfoTracker
pour écouter les modifications de mise en page.
step1/FoldingStateActor.kt
windowInfoTracker.windowLayoutInfo(activity)
.collect { newLayoutInfo ->
activeWindowLayoutInfo = newLayoutInfo
updateLayoutByFoldingState(cameraViewfinder)
}
À l'aide de l'interface WindowInfoTracker
, vous pouvez appeler windowLayoutInfo()
pour collecter un Flow
de WindowLayoutInfo
contenant toutes les informations disponibles dans l'objet DisplayFeature
.
La dernière étape consiste à coder la réaction à ces modifications et à déplacer le contenu en conséquence. Vous devez le faire dans la méthode updateLayoutByFoldingState()
, une étape à la fois.
- Veillez à ce que
activityLayoutInfo
contienne quelques propriétésDisplayFeature
, dont au moins une propriétéFoldingFeature
. Dans le cas contraire, ne faites rien :
step1/FoldingStateActor.kt
val foldingFeature = activeWindowLayoutInfo?.displayFeatures
?.firstOrNull { it is FoldingFeature } as FoldingFeature?
?: return
- Calculez la position du pli pour vous assurer que la position de l'appareil modifie votre mise en page et qu'elle ne se trouve pas en dehors des limites de votre hiérarchie :
step1/FoldingStateActor.kt
val foldPosition = FoldableUtils.getFeaturePositionInViewRect(
foldingFeature,
cameraViewfinder.parent as View
) ?: return
Maintenant que vous êtes sûr que l'objet FoldingFeature
modifie votre mise en page, vous devez déplacer votre contenu.
- Vérifiez que l'objet
FoldingFeature
a la valeurHALF_OPEN
. Autrement, restaurez simplement la position de votre contenu. Si la valeur estHALF_OPEN
, effectuez une autre vérification et agissez selon l'orientation du pli :
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()
}
Si le pli a la valeur VERTICAL
, déplacez votre contenu vers la droite. Autrement, placez-le au-dessus de la position du pli.
- Créez et exécutez votre application, puis dépliez votre appareil et placez-le en mode sur table pour voir le contenu se déplacer en conséquence !
7. Félicitations !
Dans cet atelier de programmation, vous avez découvert certaines fonctionnalités propres aux appareils pliables, telles que le mode d'affichage arrière ou le mode sur table, et vous avez appris à les débloquer à l'aide de Jetpack WindowManager.
Vous êtes prêt à implémenter des expériences utilisateur exceptionnelles pour votre application d'appareil photo.