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:
Kotlin
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. } } } }
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. } } } }
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:
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á 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.
Kotlin
if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) { val postures = WindowInfoTracker.getOrCreate(context).supportedPostures if (postures.contains(TABLE_TOP)) { // Device supports tabletop posture. } }
Java
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:
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 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