Optimiser votre application d'appareil photo sur les appareils pliables avec Jetpack WindowManager

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.

c5e52933bcd81859.png

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

  1. 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
  1. 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.

Capture d'écran d'Android Studio montrant le code en lien avec cet atelier de programmation

Si vous êtes invité à utiliser la dernière version de Gradle, continuez et mettez-la à jour.

3. Exécuter et observer

  1. 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.

149e3f9841af7726.png

  1. 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 :

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.

  1. 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
  1. 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()
      }
  }
}
  1. 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..

  1. Implémentez maintenant la fonction toggleRearDisplayMode, qui fermera la session si la capacité est déjà active, ou appelez la fonction 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
            )
        }
    }
}

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.

  1. Modifiez la déclaration MainActivity de sorte qu'elle implémente l'interface WindowAreaSessionCallback :

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]")
}
  1. 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 :

ba878f120b7c8d58.png

  1. 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.

  1. Écoutez les modifications des fenêtres dans la méthode onResume() de votre Activity :

step1/MainActivity.kt

lifecycleScope.launch {
    foldingStateActor.checkFoldingState(
         this@MainActivity, 
         binding.viewFinder
    )
}
  1. Ouvrez maintenant le fichier FoldingStateActor puisqu'il est temps de remplir la méthode checkFoldingState().

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.

  1. Veillez à ce que activityLayoutInfo contienne quelques propriétés DisplayFeature, 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
  1. 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.

  1. Vérifiez que l'objet FoldingFeature a la valeur HALF_OPEN. Autrement, restaurez simplement la position de votre contenu. Si la valeur est HALF_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.

  1. 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.

Complément d'informations

Référence