Telas grandes desdobradas e estados dobrados exclusivos permitem novas experiências do usuário em dispositivos dobráveis. Para que o app reconheça um dispositivo dobrável, use a biblioteca Jetpack WindowManager, que tem uma superfície de API para recursos de janela de dispositivos dobráveis, como articulações e dobras. Quando o app reconhece dobras, ele pode adaptar o layout para evitar colocar conteúdo importante na área delas ou de articulações, além de poder usar as dobras e articulações como separadores naturais.
Informações da janela
A interface WindowInfoTracker
na Jetpack WindowManager expõe informações de layout de janelas. O método windowLayoutInfo()
da interface retorna um fluxo de dados do WindowLayoutInfo
que informa ao app sobre o estado da dobra de um dispositivo dobrável. O método getOrCreate()
do WindowInfoTracker
cria uma instância do WindowInfoTracker
.
A WindowManager permite coletar dados WindowLayoutInfo
usando fluxos do Kotlin e callbacks do Java.
Fluxos do Kotlin
Para iniciar e interromper a coleta de dados de WindowLayoutInfo
, use uma corrotina reiniciável que reconhece o ciclo de vida, em que o bloco de código repeatOnLifecycle
é executado quando o ciclo de vida é de pelo menos STARTED
(iniciado) e interrompido quando o ciclo de vida é STOPPED
(parado). A execução do bloco de código é reiniciada automaticamente quando o ciclo de vida é STARTED
(iniciado) novamente. No exemplo abaixo, o bloco de código coleta e usa dados de WindowLayoutInfo
:
class DisplayFeaturesActivity : AppCompatActivity() {
private lateinit var binding: ActivityDisplayFeaturesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
setContentView(binding.root)
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
.windowLayoutInfo(this@DisplayFeaturesActivity)
.collect { newLayoutInfo ->
// Use newLayoutInfo to update the layout.
}
}
}
}
}
Callbacks do Java
A camada de compatibilidade de callback incluída na dependência androidx.window:window-java
permite coletar atualizações de WindowLayoutInfo
sem usar um fluxo Kotlin. O artefato inclui a classe WindowInfoTrackerCallbackAdapter
, que adapta um WindowInfoTracker
para oferecer suporte ao registro (e ao cancelamento) de callbacks para receber atualizações de WindowLayoutInfo
, por exemplo:
public class SplitLayoutActivity extends AppCompatActivity {
private WindowInfoTrackerCallbackAdapter windowInfoTracker;
private ActivitySplitLayoutBinding binding;
private final LayoutStateChangeCallback layoutStateChangeCallback =
new LayoutStateChangeCallback();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
windowInfoTracker =
new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}
@Override
protected void onStart() {
super.onStart();
windowInfoTracker.addWindowLayoutInfoListener(
this, Runnable::run, layoutStateChangeCallback);
}
@Override
protected void onStop() {
super.onStop();
windowInfoTracker
.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}
class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
@Override
public void accept(WindowLayoutInfo newLayoutInfo) {
SplitLayoutActivity.this.runOnUiThread( () -> {
// Use newLayoutInfo to update the layout.
});
}
}
}
Suporte ao RxJava
Se você já usa o RxJava
(versão 2
ou 3
), pode aproveitar os artefatos que permitem usar Observable
ou Flowable
para coletar atualizações de WindowLayoutInfo
sem usar um fluxo Kotlin (links em inglês).
A camada de compatibilidade fornecida pelas dependências de androidx.window:window-rxjava2
e androidx.window:window-rxjava3
inclui os métodos WindowInfoTracker#windowLayoutInfoFlowable()
e WindowInfoTracker#windowLayoutInfoObservable()
, que permitem que o app receba atualizações de WindowLayoutInfo
, por exemplo:
class RxActivity: AppCompatActivity {
private lateinit var binding: ActivityRxBinding
private var disposable: Disposable? = null
private lateinit var observable: Observable<WindowLayoutInfo>
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Create a new observable
observable = WindowInfoTracker.getOrCreate(this@RxActivity)
.windowLayoutInfoObservable(this@RxActivity)
}
@Override
protected void onStart() {
super.onStart();
// Subscribe to receive WindowLayoutInfo updates
disposable?.dispose()
disposable = observable
.observeOn(AndroidSchedulers.mainThread())
.subscribe { newLayoutInfo ->
// Use newLayoutInfo to update the layout
}
}
@Override
protected void onStop() {
super.onStop();
// Dispose the WindowLayoutInfo observable
disposable?.dispose()
}
}
Recursos de telas dobráveis
A classe WindowLayoutInfo
da Jetpack WindowManager disponibiliza os recursos de uma janela de exibição como uma lista de elementos DisplayFeature
.
Um FoldingFeature
é um tipo de DisplayFeature
que fornece informações sobre telas dobráveis, incluindo o seguinte:
state
: o estado dobrado do dispositivo,FLAT
ouHALF_OPENED
.orientation
: a orientação da dobra ou articulação,HORIZONTAL
ouVERTICAL
.occlusionType
: indica se a dobra ou articulação oculta parte da tela,NONE
ouFULL
.isSeparating
: se a dobra ou articulação cria duas áreas de exibição lógicas ou não, "true" ou "false".
Um dispositivo dobrável que está HALF_OPENED
sempre informa isSeparating
como "true" porque a tela é separada em duas áreas de exibição. Além disso, isSeparating
é sempre "true" em um dispositivo de tela dupla quando o app abrange as duas telas.
A propriedade FoldingFeature
bounds
(herdada de DisplayFeature
) representa o retângulo delimitador de um recurso dobrável, como uma dobra ou articulação. Os limites podem ser usados para posicionar elementos na tela em relação ao recurso.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { // Safely collects from windowInfoRepo when the lifecycle is STARTED // and stops collection when the lifecycle is STOPPED WindowInfoTracker.getOrCreate(this@MainActivity) .windowLayoutInfo(this@MainActivity) .collect { layoutInfo -> // New posture information val foldingFeature = layoutInfo.displayFeatures .filterIsInstance() .firstOrNull() // Use information from the foldingFeature object } } } }
Java
private WindowInfoTrackerCallbackAdapter windowInfoTracker; private final LayoutStateChangeCallback layoutStateChangeCallback = new LayoutStateChangeCallback(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { ... windowInfoTracker = new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this)); } @Override protected void onStart() { super.onStart(); windowInfoTracker.addWindowLayoutInfoListener( this, Runnable::run, layoutStateChangeCallback); } @Override protected void onStop() { super.onStop(); windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback); } class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> { @Override public void accept(WindowLayoutInfo newLayoutInfo) { // Use newLayoutInfo to update the Layout List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures(); for (DisplayFeature feature : displayFeatures) { if (feature instanceof FoldingFeature) { // Use information from the feature object } } } }
Modo de mesa
Usando as informações incluídas no objeto FoldingFeature
, o app pode oferecer suporte a posições como o modo de mesa,
em que o smartphone está em uma superfície, a articulação está em uma posição horizontal e a tela dobrável está meio aberta.
O modo de mesa oferece aos usuários a conveniência de operar o smartphone sem segurar o dispositivo nas mãos. O modo de mesa é ótimo para assistir conteúdo de mídia, tirar fotos e fazer videochamadas.
Use FoldingFeature.State
e FoldingFeature.Orientation
para determinar se o dispositivo está no modo de mesa:
Kotlin
fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL }
Java
boolean isTableTopPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL); }
Quando você detectar que o dispositivo está no modo de mesa, atualize o layout do app corretamente. Em apps de música, isso normalmente significa colocar a reprodução acima da dobra e posicionar os controles e o conteúdo suplementar logo abaixo para uma experiência de visualização ou escuta viva-voz.
Exemplos
App
MediaPlayerActivity
(link em inglês): confira como usar o Exoplayer da Media3 e o WindowManager para criar um player de vídeo com reconhecimento de dobra.Codelab Conheça os recursos da câmera em dispositivos dobráveis: aprenda a implementar o modo de mesa para apps de fotografia. Mostre o visor na metade de cima da tela, acima da dobra, e os controles na metade de baixo, abaixo da dobra.
Modo de livro
Outra posição dobrável exclusiva é o modo de livro, em que o dispositivo fica meio aberto com a articulação na vertical. O modo de livro é ótimo para ler e-books. Com um layout de duas páginas em uma tela dobrável grande, aberta como um livro encadernado, o modo de livro reproduz a experiência de ler um livro real.
Ele também pode ser usado para fotografia se você quiser capturar uma proporção diferente ao tirar fotos por viva-voz.
Implemente o modo de livro com as mesmas técnicas usadas no modo de mesa. A única diferença é que o código precisa conferir se a orientação do recurso dobrável é vertical em vez de horizontal:
Kotlin
fun isBookPosture(foldFeature : FoldingFeature?) : Boolean { contract { returns(true) implies (foldFeature != null) } return foldFeature?.state == FoldingFeature.State.HALF_OPENED && foldFeature.orientation == FoldingFeature.Orientation.VERTICAL }
Java
boolean isBookPosture(FoldingFeature foldFeature) { return (foldFeature != null) && (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) && (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL); }
Mudanças no tamanho das janelas
A área de exibição de um app pode mudar devido a uma modificação na configuração do dispositivo. Por exemplo, quando o dispositivo é dobrado, desdobrado, girado ou uma janela é redimensionada no modo de várias janelas.
A classe WindowMetricsCalculator
da Jetpack WindowManager permite extrair as métricas atuais e máximas da janela. Semelhante à plataforma WindowMetrics
introduzida no nível 30 da API, a WindowMetrics
da biblioteca WindowManager fornece os limites de janela, mas a API é compatível com versões anteriores até o nível 14 da API.
Consulte Classes de tamanho de janela.
Outros recursos
Exemplos (em inglês)
- Jetpack WindowManager: exemplo de como usar a biblioteca WindowManager do Jetpack.
- Jetcaster: implementação da postura de mesa com o Compose.
Codelabs
- Oferecer suporte a dispositivos dobráveis e de duas telas usando a biblioteca do Jetpack WindowManager
- Conheça os recursos da câmera em dispositivos dobráveis
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Oferecer suporte a dispositivos dobráveis e de duas telas usando a biblioteca do Jetpack WindowManager
- Otimizar o app de câmera em dispositivos dobráveis com a Jetpack WindowManager
- Modo de compatibilidade do dispositivo