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 dobras e articulações. Quando o app reconhece dobras, ele pode adaptar o layout para evitar colocar conteúdo importante na área de dobras ou articulações e usar as dobras e articulações como separadores naturais.
Entender se um dispositivo oferece suporte a configurações, como postura de mesa ou livro, pode orientar decisões sobre como oferecer suporte a diferentes layouts ou fornecer recursos específicos.
Informações da janela
A interface WindowInfoTracker
no 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 de dobra de um
dispositivo dobrável. O método WindowInfoTracker#getOrCreate()
cria uma
instância de WindowInfoTracker
.
A WindowManager oferece suporte à coleta de dados WindowLayoutInfo
usando
fluxos Kotlin e callbacks do Java.
Fluxos 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
e é interrompido quando o
ciclo de vida é STOPPED
. 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
),
aproveite os artefatos que permitem usar um
Observable
ou Flowable
para coletar atualizações de WindowLayoutInfo
sem usar um fluxo Kotlin.
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 of 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, "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:
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Safely collects from WindowInfoTracker 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<FoldingFeature>()
.firstOrNull()
// Use information from the foldingFeature object.
}
}
}
}
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.
}
}
}
}
Posição de mesa
Usando as informações incluídas no objeto FoldingFeature
, o app pode
oferecer suporte a posições como uma 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.
A postura de mesa oferece aos usuários a conveniência de operar o smartphone sem segurar o dispositivo nas mãos. A postura de mesa é ótima para assistir conteúdo de mídia, tirar fotos e fazer videochamadas.

Use FoldingFeature.State
e FoldingFeature.Orientation
para determinar
se o dispositivo está na posição de mesa:
fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
contract { returns(true) implies (foldFeature != null) }
return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
}
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á na posição de mesa, atualize o layout do app corretamente. Em apps de mídia, 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.
No Android 15 (nível 35 da API) e versões mais recentes, é possível invocar uma API síncrona para detectar se um dispositivo oferece suporte à postura de mesa, independente do estado atual dele.
A API fornece uma lista de posturas com suporte do dispositivo. Se a lista contiver a postura de mesa, você poderá dividir o layout do app para oferecer suporte a ela e executar testes A/B na interface do app para layouts de mesa e tela cheia.
if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) {
val postures = WindowInfoTracker.getOrCreate(context).supportedPostures
if (postures.contains(TABLE_TOP)) {
// Device supports tabletop posture.
}
}
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures();
if (postures.contains(SupportedPosture.TABLETOP)) {
// Device supports tabletop posture.
}
}
Exemplos
App
MediaPlayerActivity
: confira como usar o Exoplayer da Media3 e o WindowManager para criar um player de vídeo com reconhecimento de dobra.Codelab Otimize o app de câmera em dispositivos dobráveis com o Jetpack WindowManager: aprenda a implementar a posição 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).
Posição de livro
Outro recurso dobrável exclusivo é a postura de livro, em que o dispositivo fica meio aberto com a articulação na vertical. A posição de livro é ótima para ler e-books. Com um layout de duas páginas em uma tela dobrável grande aberta como um livro encadernado, a postura do livro captura 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 a postura de livro com as mesmas técnicas usadas para a postura 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:
fun isBookPosture(foldFeature : FoldingFeature?) : Boolean {
contract { returns(true) implies (foldFeature != null) }
return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
}
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 como resultado de uma mudança na configuração do dispositivo, por exemplo, quando o dispositivo é dobrado ou 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 Usar classes de tamanho de janela.
Outros recursos
Amostras
- 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
- Otimizar o app de câmera em dispositivos dobráveis com a Jetpack WindowManager