1. Antes de começar
O que há de especial nos dispositivos dobráveis?
Os dispositivos dobráveis são inovações que marcam uma geração. Eles proporcionam experiências exclusivas e, com elas, oportunidades para encantar seus usuários com recursos diferenciados, como a interface do usuário de mesa para uso do viva-voz.
Pré-requisitos
- Noções básicas de desenvolvimento de apps Android
- Noções básicas do framework da injeção de dependências com o Hilt
O que você vai criar
Neste codelab, você vai criar um app de câmera com layouts otimizados para dispositivos dobráveis.

Você começa com um app de câmera básico que não considera as mudanças de posição dos dispositivos ou aproveita a câmera traseira de melhor qualidade para tirar selfies aprimoradas. Você atualiza o código-fonte para mover a visualização para a tela menor quando o dispositivo for desdobrado e reage ao smartphone sendo configurado no modo de mesa.
Embora o app da câmera seja o caso de uso mais conveniente para esta API, os dois recursos que você aprenderá neste codelab podem ser aplicados a qualquer app.
O que você vai aprender
- Como usar o Jetpack Window Manager para levar em conta a mudança de posição do dispositivo
- Como mover o app para a tela menor de um dispositivo dobrável
O que é necessário
- Uma versão recente do Android Studio.
- Um dispositivo ou emulador dobráveis
2. Começar a configuração
Fazer o download do código inicial
- Se você tiver o Git instalado, basta executar o comando abaixo. Para verificar se o Git está instalado, digite
git --versionno terminal ou na linha de comando e verifique se ele é executado corretamente.
git clone https://github.com/android/large-screen-codelabs.git
- Opcional: caso não tenha o Git, clique no botão abaixo para fazer o download de todo o código deste codelab:
Abrir o primeiro módulo
- No Android Studio, abra o primeiro módulo em
/step1.

Se for solicitado que você use a versão mais recente do Gradle, atualize-a.
3. Executar e observar
- Execute o código no módulo
step1.
Como você pode perceber, este é um app de câmera simples. É possível alternar entre a câmera frontal e a traseira e ajustar a proporção. No entanto, o primeiro botão da esquerda atualmente não faz nada, mas será o ponto de entrada para o modo selfie traseira.

- Agora, tente colocar o dispositivo na posição semi-aberta, em que a dobradiça não fica totalmente plana ou fechada, mas forma um ângulo de 90 graus.
Como você pode perceber, o app não responde às posições diferentes do dispositivo e, portanto, o layout não muda, deixando a dobra no meio do visor.
4. Saiba mais sobre o Jetpack WindowManager
A biblioteca Jetpack WindowManager ajuda os desenvolvedores de apps a criar experiências otimizadas para dispositivos dobráveis. Ela contém a classe FoldingFeature, que descreve uma dobra em telas flexíveis ou uma articulação entre dois painéis de tela física. A API dela fornece acesso a informações importantes relacionadas ao dispositivo:
state()vai retornarFLATse a dobra for aberta a 180 graus ouHALF_OPENEDde outra forma.orientation()retornaFoldingFeature.Orientation.HORIZONTALse a largura deFoldingFeaturefor maior que a altura. Caso contrário, o retorno éFoldingFeature.Orientation.VERTICAL.bounds()fornece os limites deFoldingFeatureem um formatoRect.
A classe FoldingFeature contém outras informações, como occlusionType() ou isSeparating(), mas este codelab não os explora em profundidade.
A partir da versão 1.1.0-beta01, a biblioteca usa a WindowAreaController, uma API que permite que o modo de tela traseira mova a janela atual para a tela alinhada com a câmera traseira, o que é ótimo para tirar selfies com a câmera traseira e muitos outros casos de uso.
Adicionar dependências
- Para usar o Jetpack WindowManager no app, adicione as seguintes dependências ao nível do módulo do arquivo
build.gradle:
step1/build.gradle
def work_version = '1.1.0-beta01'
implementation "androidx.window:window:$work_version"
implementation "androidx.window:window-java:$work_version"
implementation "androidx.window:window-core:$work_version"
Agora você pode acessar as classes FoldingFeature e WindowAreaController no app. Elas servem para criar a melhor experiência de câmera dobrável.
5. Implementar o modo selfie traseira
Comece com o modo de tela traseira. A API que habilita esse modo é a WindowAreaControllerJavaAdapter, que requer Executor e retorna WindowAreaSession, que armazena o estado atual. Este WindowAreaSession deve ser mantido quando Activity for excluído e recriado, então você o armazena dentro de ViewModel para armazená-lo com segurança nas alterações de configuração.
- Declare essas variáveis em
MainActivity:
step1/MainActivity.kt
private lateinit var windowAreaController: WindowAreaControllerJavaAdapter
private lateinit var displayExecutor: Executor
- E inicialize-os no método
onCreate():
step1/MainActivity.kt
windowInfoTracker = WindowInfoTracker.getOrCreate(this)
displayExecutor = ContextCompat.getMainExecutor(this)
windowAreaController = WindowAreaControllerJavaAdapter(WindowAreaController.getOrCreate())
Agora o Activity está pronto para mover o conteúdo na tela menor, mas você precisa armazenar a sessão.
- Para armazenar a sessão, abra
CameraViewModele declare esta variável dentro dela:
step1/CameraViewModel.kt
var rearDisplaySession: WindowAreaSession? = null
private set
Você precisa que rearDisplaySession seja uma variável, já que ela muda toda vez que você cria uma, mas você quer ter certeza de que ela não seja atualizada de fora, porque agora você cria um método que a atualiza sempre que for necessário.
- Cole este código dentro de
CameraViewModel:
step1/CameraViewModel.kt
fun updateSession(newSession: WindowAreaSession? = null) {
rearDisplaySession = newSession
}
Esse método é invocado toda vez que o código precisa atualizar a sessão e convém encapsulá-lo em um único ponto de acesso.
A API Rear Display funciona com uma abordagem de listener: quando você move o conteúdo para a tela menor, uma sessão é iniciada e retornada usando o método onSessionStarted() do listener. Quando, em vez disso, você quer retornar à tela interna (e maior), você fecha a sessão e recebe uma confirmação no método onSessionEnded(). Você usa esses métodos para atualizar rearDisplaySession no interior de CameraViewModel. Para criar esse listener, você precisa implementar a interface WindowAreaSessionCallback.
- Modifique a declaração
MainActivitypara que ela implemente a interface deWindowAreaSessionCallback:
step1/MainActivity.kt
class MainActivity : AppCompatActivity(), WindowAreaSessionCallback
Agora, implemente os métodos onSessionStarted e onSessionEnded dentro de MainActivity. Para o primeiro, você precisa salvar o WindowAreaSession e, no segundo, redefini-lo para null. Isso é particularmente útil, porque a presença de WindowAreaSession permite que você decida se quer iniciar uma sessão nova ou fechar uma existente.
step1/MainActivity.kt
override fun onSessionEnded() {
viewModel.updateSession(null)
}
override fun onSessionStarted(session: WindowAreaSession) {
viewModel.updateSession(session)
}
- No arquivo
MainActivity.kt, escreva o último trecho de código necessário para essa API funcionar:
step1/MainActivity.kt
private fun startRearDisplayMode() {
if (viewModel.rearDisplaySession != null) {
viewModel.rearDisplaySession?.close()
} else {
windowAreaController.startRearDisplayModeSession(
this,
displayExecutor,
this
)
}
}
Como mencionamos anteriormente, para entender qual ação tomar, você precisa verificar a presença de rearDisplaySession dentro de CameraViewModel: se não for null, a sessão já está ocorrendo, então ela fecha. Por outro lado, se for null, você usa windowAreaController para iniciar uma nova sessão, transmitindo Activity duas vezes. A primeira vez é usada como Context e a segunda como um listener WindowAreaSessionCallback.
- Agora, crie e execute o app. Se você abrir o dispositivo e tocar no botão da tela traseira, uma mensagem como esta será exibida:

- Clique em Troque de tela agora e seu conteúdo será movido para a tela externa!
6. Implementar o modo de mesa
Agora é hora de tornar o app sensível à dobra: mover o conteúdo para a lateral ou acima da dobra do dispositivo com base na orientação da dobra. Para fazer isso, você estará agindo dentro de FoldingStateActor para que o código seja desacoplado de Activity para facilitar a leitura.
A parte central desta API consiste na interface WindowInfoTracker, que é criada com um método estático que requer um Activity:
step1/CameraCodelabDependencies.kt
@Provides
fun provideWindowInfoTracker(activity: Activity) =
WindowInfoTracker.getOrCreate(activity)
Você não precisa escrever esse código porque ele já está presente, mas é útil entender como WindowInfoTracker é criado.
- Detecte todas as mudança de janela no método
onResume()deActivity:
step1/MainActivity.kt
lifecycleScope.launch {
foldingStateActor.checkFoldingState(
this@MainActivity,
binding.viewFinder
)
}
- Agora, abra o arquivo
FoldingStateActor, porque é hora de preencher o métodocheckFoldingState().
Como você já percebeu, ele é executado na fase RESUMED de Activity e aproveita WindowInfoTracker para detectar qualquer alteração de layout.
step1/FoldingStateActor.kt
windowInfoTracker.windowLayoutInfo(activity)
.collect { newLayoutInfo ->
activeWindowLayoutInfo = newLayoutInfo
updateLayoutByFoldingState(cameraViewfinder)
}
Ao usar a interface WindowInfoTracker, você pode chamar windowLayoutInfo() para coletar um Flow de WindowLayoutInfo que contém todas as informações disponíveis em DisplayFeature.
A última etapa é reagir a essas mudanças e mover o conteúdo de acordo. Você faz isso dentro do método updateLayoutByFoldingState(), um passo de cada vez.
- Certifique-se que
activityLayoutInfocontenha algumas propriedadesDisplayFeaturee que pelo menos uma delas sejaFoldingFeature. Caso contrário, você não precisa fazer nada:
step1/FoldingStateActor.kt
val foldingFeature = activeWindowLayoutInfo?.displayFeatures
?.firstOrNull { it is FoldingFeature } as FoldingFeature?
?: return
- Calcule a posição da dobra para garantir que a posição do dispositivo esteja afetando o layout e não esteja fora dos limites de hierarquia:
step1/FoldingStateActor.kt
val foldPosition = FoldableUtils.getFeaturePositionInViewRect(
foldingFeature,
cameraViewfinder.parent as View
) ?: return
Agora, você tem certeza de que tem um FoldingFeature que impacta o layout, então você precisa mover o conteúdo.
- Verifique se
FoldingFeatureforHALF_OPENou outro, apenas restaure a posição do conteúdo. Se forHALF_OPEN, você precisa executar outra verificação e agir de maneira diferente com base na orientação da dobra:
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 a dobra for VERTICAL, mova o conteúdo para a direita. Caso contrário, mova-o para cima da posição da dobra.
- Crie e execute o app e, em seguida, desdobre o dispositivo e coloque-o no modo de mesa para que o conteúdo se mova.
7. Parabéns
Neste codelab, você aprendeu o que há de especial nos dispositivos dobráveis, nas mudanças de posição e na API Rear Display.