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 --version
no 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 retornarFLAT
se a dobra for aberta a 180 graus ouHALF_OPENED
de outra forma.orientation()
retornaFoldingFeature.Orientation.HORIZONTAL
se a largura deFoldingFeature
for maior que a altura. Caso contrário, o retorno éFoldingFeature.Orientation.VERTICAL
.bounds()
fornece os limites deFoldingFeature
em 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
CameraViewModel
e 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
MainActivity
para 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
activityLayoutInfo
contenha algumas propriedadesDisplayFeature
e 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
FoldingFeature
forHALF_OPEN
ou 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.