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.
Entender se um dispositivo oferece suporte a configurações como a posição de mesa ou de livro pode orientar as decisões sobre o suporte a layouts diferentes ou o fornecimento de recursos específicos.
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 WindowLayoutInfo que informa ao app sobre o estado da dobra de um dispositivo dobrável. O WindowInfoTracker#getOrCreate() método cria uma
instância de WindowInfoTracker.
A WindowManager oferece suporte à coleta de dados WindowLayoutInfo usando fluxos do Kotlin e callbacks do Java.
Fluxos Kotlin
Para iniciar e interromper a coleta de dados 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 novamente. No exemplo abaixo, o bloco de código coleta e usa dados de WindowLayoutInfo:
class DisplayFeaturesActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { // ... 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
androidx.window:window-java dependência permite coletar
WindowLayoutInfo atualizações sem usar um fluxo Kotlin. O artefato inclui
a WindowInfoTrackerCallbackAdapter classe, que adapta um
WindowInfoTracker para oferecer suporte ao registro (e ao cancelamento) de callbacks para
receber WindowLayoutInfo atualizações, 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.
A camada de compatibilidade fornecida pelas androidx.window:window-rxjava2 e
androidx.window:window-rxjava3 dependências inclui os
WindowInfoTracker#windowLayoutInfoFlowable() e
WindowInfoTracker#windowLayoutInfoObservable() métodos, que permitem que o
app receba WindowLayoutInfo atualizações, 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 = ActivityRxBinding.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 DisplayFeature elementos.
Um FoldingFeature é um tipo de DisplayFeature que fornece informações
sobre telas dobráveis, incluindo as seguintes propriedades:
state: o estado dobrado do dispositivo,FLATouHALF_OPENED.orientation: a orientação da dobra ou articulação,HORIZONTALouVERTICAL.occlusionType: indica se a dobra ou articulação oculta parte da tela,NONEouFULLisSeparating: indica se a dobra ou articulação cria duas áreas de exibição lógicas, "true" ou "false".
Um dispositivo dobrável que é HALF_OPENED sempre informa isSeparating como verdadeiro porque a tela é separada em duas áreas de exibição. Além disso, isSeparating é sempre verdadeiro 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 a 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.
A posição de mesa oferece aos usuários a conveniência de operar o smartphone sem segurar o dispositivo nas mãos. A posição 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 à posição de mesa, independente do estado atual do dispositivo.
A API fornece uma lista de posições com suporte do dispositivo. Se a lista contiver a posição de mesa, você poderá dividir o layout do app para oferecer suporte à posição e executar testes A/B na interface do app para layouts de mesa e tela cheia.
Kotlin
if (WindowSdkExtensions.getInstance().extensionVersion >= 6) { val postures = WindowInfoTracker.getOrCreate(context).supportedPostures if (postures.contains(SupportedPosture.TABLETOP)) { // 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
MediaPlayerActivityapp: confira como usar o Exoplayer da Media3 e o WindowManager para criar um player de vídeo com reconhecimento de dobra.Otimize seu app de câmera em dispositivos dobráveis com o Jetpack WindowManager codelab: 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 posição 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 posição 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 a posição de livro com as mesmas técnicas usadas na posição 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 da API 30, a WindowManager
WindowMetrics fornece os limites de janela, mas a API é compatível com versões anteriores
até o nível da API 14.
Consulte Usar classes de tamanho de janela.
Outros recursos
Amostras
- Jetpack WindowManager: exemplo de como usar a biblioteca WindowManager do Jetpack .
- Jetcaster (link em inglês): implementação da posição 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 o Jetpack WindowManager