Manual para telas grandes

O Android oferece tudo o que você precisa para criar apps de tela grande dignos de notas cinco estrelas. As instruções deste manual mostram e combinam dicas para ajudar você a resolver problemas de desenvolvimento específicos. Cada seção inclui práticas recomendadas, exemplos de códigos de qualidade e orientações detalhadas para ajudar você a tornar seu app ainda melhor.

Notas

As seções estão classificadas por estrelas com base no alinhamento delas com as diretrizes de qualidade de apps para telas grandes.

Cinco estrelas Atende aos critérios de nível 1, diferenciada para telas grandes
Quatro estrelas Atende aos critérios de nível 2, otimizada para telas grandes
Três estrelas Atende aos critérios de nível 3, pronta para telas grandes
Duas estrelas Fornece alguns recursos de tela grande, mas não atende às diretrizes de qualidade de apps para telas grandes
Uma estrela Atende às necessidades de um caso de uso específico, mas não oferece suporte a telas grandes

Suporte à câmera do Chromebook

Três estrelas

Aparece no Google Play para os usuários do Chromebook.

Caso seu app de câmera funcione com os recursos básicos da câmera, não permita que as app stores impeçam que os usuários do Chromebook instalem o app só porque você especificou acidentalmente recursos avançados de câmera encontrados em smartphones de última geração.

Os Chromebooks têm uma câmera frontal integrada (voltada para o usuário) que funciona bem para videoconferências, retratos e outros fins. Porém, nem todos os Chromebooks têm uma câmera traseira (voltada para o mundo), e a maioria das câmeras voltadas para o usuário em Chromebooks não oferecem suporte a foco automático ou flash.

Práticas recomendadas

Apps versáteis de câmera oferecem suporte a todos os dispositivos, independente da configuração da câmera: frontal, traseira ou externa conectada por USB.

Para garantir que as app stores disponibilizem o app para o maior número possível de dispositivos, sempre declare todos os recursos da câmera usados pelo app e indique explicitamente se os recursos são ou não obrigatórios.

Componentes

  • Permissão da CAMERA: dá ao app acesso às câmeras de um dispositivo.
  • Elemento de manifesto <uses-feature>: informa as app stores sobre os recursos usados pelo app.
  • Atributo required: indica às app stores se o app pode funcionar sem um recurso especificado.

Etapas

Resumo

Declare a permissão CAMERA. Declare os recursos da câmera que oferecem suporte básico a câmeras. Especifique se cada recurso é obrigatório.

1. Declare a permissão CAMERA

Adicione a permissão abaixo ao manifesto do app:

<uses-permission android:name="android.permission.CAMERA" />
2. Declare as funcionalidades básicas da câmera

Adicione os recursos abaixo ao manifesto do app:

<uses-feature android:name="android.hardware.camera.any" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
3. Especifique se cada recurso é obrigatório ou não

Defina android:required="false" para o recurso android.hardware.camera.any para permitir que dispositivos que têm qualquer tipo de câmera integrada ou externa (ou nenhuma câmera) possam usar o app.

Para os outros recursos, defina android:required="false" para garantir que dispositivos, como Chromebooks que não tenham câmeras traseiras, foco automático ou flash possam acessar o aplicativo em app stores.

Resultados

Os usuários de Chromebooks podem fazer o download e instalar o app com o Google Play e outras app stores. Além disso, dispositivos com suporte total a câmeras, como smartphones, não vão ter restrições de funcionalidade.

Ao definir explicitamente os recursos da câmera com suporte do app e especificar os recursos exigidos, você disponibiliza o app para o maior número possível de dispositivos.

Outros recursos

Para mais informações, consulte Recursos de hardware da câmera na documentação de <uses-feature>.

Orientação restrita do app em smartphones, mas não em dispositivos de tela grande

Duas estrelas

Como o app funciona muito bem em smartphones na orientação retrato, você o restringiu apenas a esse modo. Mas você percebe uma oportunidade de melhorar o suporte do app em telas grandes na orientação paisagem.

Como você pode aproveitar o melhor de dois mundos, restringindo o app à orientação retrato em telas pequenas, mas ativando o modo paisagem em telas grandes?

Práticas recomendadas

Os melhores apps respeitam as preferências do usuário, como a orientação do dispositivo.

As diretrizes de qualidade de apps para telas grandes recomendam que os apps ofereçam suporte a todas as configurações de dispositivos, incluindo orientações de retrato e paisagem, modo de várias janelas e estados dobrados e desdobrados. Os apps precisam otimizar os layouts e as interfaces do usuário para diferentes configurações e também precisam salvar e restaurar o estado durante as mudanças.

Esta seção apresenta apenas uma medida temporária, oferecendo o mínimo de suporte para telas grandes. Use estas dicas até melhorar o app o suficiente para oferecer suporte total a todas as configurações de dispositivos.

Componentes

  • screenOrientation: configuração do manifesto do app que permite especificar como o aplicativo responde às mudanças de orientação do dispositivo.
  • Jetpack WindowManager: conjunto de bibliotecas que permitem determinar o tamanho e a proporção da janela do app. Ela é compatível com o nível 14 da API e versões mais recentes.
  • Activity#setRequestedOrientation(): método com que é possível mudar a orientação do app no momento da execução.

Etapas

Resumo

Ativa o app para processar mudanças de orientação por padrão no manifesto dele. Durante a execução, determine o tamanho da janela do app. Se a janela do app for pequena, restrinja a orientação do app substituindo a configuração de orientação no manifesto.

1. Especificar a configuração de orientação no manifesto do app

Você pode evitar declarar o elemento screenOrientation do manifesto do app, o que define a orientação padrão como unspecified, ou pode definir a orientação da tela como fullUser. Se o usuário não tiver bloqueado a rotação com base no sensor, o app vai oferecer suporte a todas as orientações do dispositivo.

<activity
    android:name=".MyActivity"
    android:screenOrientation="fullUser">

A diferença entre usar unspecified e fullUser é sutil, mas importante. Se você não declarar um valor screenOrientation, o sistema vai escolher a orientação, e a política usada para defini-la poderá variar de acordo com o dispositivo. Por outro lado, a especificação de fullUser corresponde melhor ao comportamento que o usuário definiu para o dispositivo: se ele bloqueou a rotação com base no sensor, o app segue a preferência do usuário. Caso contrário, o sistema permite qualquer uma das quatro orientações de tela possíveis (retrato, paisagem, retrato invertido ou paisagem invertida). Consulte android:screenOrientation.

2. Determinar o tamanho da tela

Com o manifesto definido para oferecer suporte a todas as orientações permitidas pelo usuário, você pode especificar a orientação do app de forma programática com base no tamanho da tela.

Adicione as bibliotecas do Jetpack WindowManager ao arquivo build.gradle ou build.gradle.kts do módulo:

Kotlin

implementation("androidx.window:window:version")
implementation("androidx.window:window-core:version")

Groovy

implementation 'androidx.window:window:version'
implementation 'androidx.window:window-core:version'

Use o método Jetpack WindowManager WindowMetricsCalculator#computeMaximumWindowMetrics() para conferir o tamanho da tela do dispositivo como um objeto WindowMetrics. As métricas da janela podem ser comparadas a classes de tamanho de janela para decidir quando restringir a orientação.

As classes de tamanho de janelas oferecem os pontos de interrupção entre telas pequenas e grandes.

Use os pontos de interrupção WindowWidthSizeClass#COMPACT e WindowHeightSizeClass#COMPACT para determinar o tamanho da tela:

Kotlin

/** Determines whether the device has a compact screen. **/
fun compactScreen() : Boolean {
    val metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this)
    val width = metrics.bounds.width()
    val height = metrics.bounds.height()
    val density = resources.displayMetrics.density
    val windowSizeClass = WindowSizeClass.compute(width/density, height/density)

    return windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT ||
        windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT
}

Java

/** Determines whether the device has a compact screen. **/
private boolean compactScreen() {
    WindowMetrics metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this);
    int width = metrics.getBounds().width();
    int height = metrics.getBounds().height();
    float density = getResources().getDisplayMetrics().density;
    WindowSizeClass windowSizeClass = WindowSizeClass.compute(width/density, height/density);
    return windowSizeClass.getWindowWidthSizeClass() == WindowWidthSizeClass.COMPACT ||
                windowSizeClass.getWindowHeightSizeClass() == WindowHeightSizeClass.COMPACT;
}
    Observação:
  • Os exemplos acima são implementados como métodos de uma atividade. A atividade é desreferenciada como this no argumento de computeMaximumWindowMetrics().
  • O método computeMaximumWindowMetrics() é usado em vez de computeCurrentWindowMetrics() porque o app pode ser iniciado no modo de várias janelas, o que ignora a configuração de orientação da tela. Não há sentido em determinar o tamanho da janela do app e substituir a configuração de orientação, a menos que a janela do app seja a tela inteira do dispositivo.

Consulte WindowManager para instruções sobre como declarar dependências e disponibilizar o método computeMaximumWindowMetrics() no app.

3. Substituir a configuração do manifesto do app

Se você determinar que o dispositivo tem um tamanho de tela compacto, chame Activity#setRequestedOrientation() para substituir a configuração screenOrientation do manifesto:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    requestedOrientation = if (compactScreen())
        ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
        ActivityInfo.SCREEN_ORIENTATION_FULL_USER
    ...
    // Replace with a known container that you can safely add a
    // view to where the view won't affect the layout and the view
    // won't be replaced.
    val container: ViewGroup = binding.container

    // Add a utility view to the container to hook into
    // View.onConfigurationChanged. This is required for all
    // activities, even those that don't handle configuration
    // changes. You can't use Activity.onConfigurationChanged,
    // since there are situations where that won't be called when
    // the configuration changes. View.onConfigurationChanged is
    // called in those scenarios.
    container.addView(object : View(this) {
        override fun onConfigurationChanged(newConfig: Configuration?) {
            super.onConfigurationChanged(newConfig)
            requestedOrientation = if (compactScreen())
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
                ActivityInfo.SCREEN_ORIENTATION_FULL_USER
        }
    })
}

Java

@Override
protected void onCreate(Bundle savedInstance) {
    super.onCreate(savedInstanceState);
    if (compactScreen()) {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    } else {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
    }
    ...
    // Replace with a known container that you can safely add a
    // view to where the view won't affect the layout and the view
    // won't be replaced.
    ViewGroup container = binding.container;

    // Add a utility view to the container to hook into
    // View.onConfigurationChanged. This is required for all
    // activities, even those that don't handle configuration
    // changes. You can't use Activity.onConfigurationChanged,
    // since there are situations where that won't be called when
    // the configuration changes. View.onConfigurationChanged is
    // called in those scenarios.
    container.addView(new View(this) {
        @Override
        protected void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            if (compactScreen()) {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            } else {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
            }
        }
    });
}

Ao adicionar a lógica aos métodos onCreate() e View.onConfigurationChanged(), você poderá acessar as métricas máximas da janela e substituir a configuração de orientação sempre que a atividade for redimensionada ou movida entre as telas, por exemplo, após uma rotação do dispositivo ou quando um dispositivo dobrável for dobrado ou desdobrado. Para mais informações sobre quando as mudanças de configuração ocorrem e quando elas causam a recriação de atividades, consulte Gerenciar mudanças de configuração.

Resultados

Agora, o app vai permanecer na orientação retrato em telas pequenas, independente da rotação do dispositivo. Em telas grandes, o app precisa oferecer suporte às orientações de paisagem e retrato.

Outros recursos

Para receber ajuda com o upgrade do app para que ele ofereça suporte a todas as configurações de dispositivos o tempo todo, consulte:

Pausar e retomar a reprodução de mídia usando a barra de espaço do teclado externo

Quatro estrelas

A otimização para telas grandes inclui a capacidade de processar entradas do teclado externo, como usar o pressionamento da barra de espaço para pausar ou retomar a reprodução de vídeos e outras mídias. Isso é especialmente útil para tablets, que geralmente se conectam a teclados externos, e para Chromebooks, que geralmente vêm com teclados externos, mas podem ser usados no modo tablet.

Quando a mídia é o único elemento da janela (por exemplo, uma reprodução de vídeo em tela cheia), responda a eventos de pressionamento de tecla no nível da atividade ou no nível da tela, no caso do Jetpack Compose.

Práticas recomendadas

Sempre que seu app reproduzir um arquivo de mídia, os usuários poderão pausar e retomar a reprodução pressionando a barra de espaço em um teclado físico.

Componentes

  • KEYCODE_SPACE: constante de código de tecla da barra de espaço.

Compose

  • onPreviewKeyEvent: o Modifier que permite a um componente interceptar eventos de teclas do hardware quando ele ou um dos filhos está em foco.
  • onKeyEvent: semelhante ao onPreviewKeyEvent, esse Modifier permite que um componente intercepte eventos de teclas do hardware quando ele ou um dos filhos está em foco.

Visualizações

  • onKeyUp(): chamado quando uma tecla é liberada e não é processada por uma visualização em uma atividade.

Etapas

Resumo

Apps baseados no Jetpack Compose ou em visualizações respondem a pressionamentos de tecla de maneiras semelhantes: o app precisa detectar eventos de pressionamento de tecla, filtrá-los e responder a pressionamentos selecionados, como o de uma barra de espaço.

1. Detectar eventos de teclado

Visualizações

Em uma atividade no app, substitua o método onKeyUp():

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
    ...
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    ...
}

O método é invocado sempre que uma tecla pressionada é solta. Portanto, ele é acionado exatamente uma vez para cada tecla pressionada.

Compose

Com o Jetpack Compose, você pode usar os modificadores onPreviewKeyEvent ou onKeyEvent na tela que gerencia o pressionamento de tecla:

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

ou

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

2. Filtrar pressionamentos da barra de espaço

No método onKeyUp() ou nos métodos modificadores onPreviewKeyEvent e onKeyEvent do Compose, filtre por KeyEvent.KEYCODE_SPACE para enviar o evento correto ao componente de mídia:

Visualizações

Kotlin

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback()
    return true
}
return false

Java

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback();
    return true;
}
return false;

Compose

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

ou

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

Resultados

Seu app agora pode responder a pressionamentos de tecla da barra de espaço para pausar e retomar um vídeo ou outra mídia.

Outros recursos

Para saber mais sobre os eventos de teclado e como gerenciá-los, consulte Gerenciar a entrada do teclado.

Rejeição da palma ao usar a stylus

Cinco estrelas

Uma stylus pode ser uma ferramenta muito produtiva e criativa em telas grandes. No entanto, quando os usuários desenham, escrevem ou interagem com o app usando uma stylus, às vezes eles tocam na tela com a palma da mão. O evento de toque pode ser informado ao app antes que o sistema reconheça e desconsidere o evento como um toque acidental da palma da mão.

Práticas recomendadas

O app precisa identificar e ignorar eventos de toque irrelevantes. O Android cancela um toque da palma da mão enviando um objeto MotionEvent. Procure ACTION_CANCEL ou ACTION_POINTER_UP e FLAG_CANCELED no objeto para determinar se o gesto causado pelo toque da palma da mão será rejeitado.

Componentes

  • MotionEvent: representa eventos de toque e movimento. Contém as informações necessárias para determinar se um evento será desconsiderado ou não.
  • OnTouchListener#onTouch(): recebe objetos MotionEvent.
  • MotionEvent#getActionMasked(): retorna a ação associada a um evento de movimento.
  • ACTION_CANCEL: constante MotionEvent que indica que um gesto precisa ser desfeito.
  • ACTION_POINTER_UP: constante MotionEvent que indica que um ponteiro diferente do primeiro foi levantado, ou seja, parou de fazer contato com a tela do dispositivo.
  • FLAG_CANCELED: constante MotionEvent que indica que o ponteiro que foi levantado causou um evento de toque não intencional. Adicionado aos eventos ACTION_POINTER_UP e ACTION_CANCEL no Android 13 (nível 33 da API) e mais recentes.

Etapas

Resumo

Examine objetos MotionEvent enviados para seu app. Use as APIs MotionEvent para determinar as características do evento:

  • Eventos de ponteiro único: procure ACTION_CANCEL. No Android 13 e mais recentes, procure também por FLAG_CANCELED.
  • Eventos com vários ponteiros: no Android 13 e mais recentes, procure ACTION_POINTER_UP e FLAG_CANCELED.

Responda a eventos ACTION_CANCEL e ACTION_POINTER_UP/FLAG_CANCELED.

1. Obter objetos de eventos de movimento

Adicione um OnTouchListener ao app:

Kotlin

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        // Process motion event.
    }
}

Java

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    // Process motion event.
});
2. Determinar a ação e as flags do evento

Procure ACTION_CANCEL, que indica um evento de ponteiro único em todos os níveis de API. No Android 13 e mais recentes, procure ACTION_POINTER_UP em FLAG_CANCELED..

Kotlin

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        when (event.actionMasked) {
            MotionEvent.ACTION_CANCEL -> {
                //Process canceled single-pointer motion event for all SDK versions.
            }
            MotionEvent.ACTION_POINTER_UP -> {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
                   (event.flags and MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                    //Process canceled multi-pointer motion event for Android 13 and higher.
                }
            }
        }
        true
    }
}

Java

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_CANCEL:
            // Process canceled single-pointer motion event for all SDK versions.
        case MotionEvent.ACTION_UP:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
               (event.getFlags() & MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                //Process canceled multi-pointer motion event for Android 13 and higher.
            }
    }
    return true;
});
3. Desfazer o gesto

Depois de identificar um toque da palma da mão, você pode desfazer os efeitos do gesto na tela.

O app precisa manter um histórico de ações do usuário para que entradas não intencionais, como toques da palma da mão, possam ser desfeitas. Consulte Implementar um app de desenho básico no codelab Melhorar o suporte à stylus em um app Android para conferir um exemplo.

Resultados

O app agora pode identificar e rejeitar toques da palma da mão em eventos de vários ponteiros no Android 13 e níveis da API mais recentes e em eventos de ponteiro único em todos os níveis da API.

Outros recursos

Para mais informações, consulte os tópicos abaixo:

Gerenciamento de estado do WebView

Três estrelas

O WebView é um componente usado com frequência e que oferece um sistema avançado para gerenciamento de estado. O WebView precisa manter o estado e a posição de rolagem durante as mudanças de configuração. Um WebView pode perder a posição de rolagem quando o usuário gira o dispositivo ou desdobra um smartphone dobrável, o que força o usuário a rolar novamente da parte de cima do WebView para a posição de rolagem anterior.

Práticas recomendadas

Minimize o número de vezes que um WebView é recriado. O WebView é bom em gerenciar o estado, e você pode aproveitar essa qualidade gerenciando o maior número possível de mudanças de configuração. Seu app precisa processar mudanças de configuração, porque a recriação da Activity (a maneira como o sistema processa essas mudanças) também recria o WebView, o que faz com que o WebView perca o estado.

Componentes

  • android:configChanges: atributo do elemento <activity> do manifesto. Lista as mudanças de configuração gerenciadas pela atividade.
  • View#invalidate(): método que faz com que uma visualização seja redesenhada. Herdado pelo WebView.

Etapas

Resumo

Para salvar o estado do WebView, evite ao máximo a recriação da Activity e deixe o WebView ser invalidado para que possa ser redimensionado, mantendo o estado.

1. Adicionar mudanças de configuração ao arquivo AndroidManifest.xml do app

Para evitar a recriação de atividades, especifique as mudanças de configuração processadas pelo app, e não pelo sistema:

<activity
  android:name=".MyActivity"
  android:configChanges="screenLayout|orientation|screenSize
      |keyboard|keyboardHidden|smallestScreenSize" />

2. Invalidar o WebView sempre que o app receber uma mudança de configuração

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    webView.invalidate()
}

Java

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    webview.invalidate();
}

Esta etapa se aplica apenas ao sistema de visualização, já que o Jetpack Compose não precisa invalidar nada para redimensionar corretamente os elementos Composable. No entanto, o Compose recria um WebView com frequência quando ele não é gerenciado corretamente. Use o wrapper Accompanist WebView para salvar e restaurar o estado do WebView nos apps do Compose.

Resultados

Os componentes do WebView do app agora mantêm o estado e a posição de rolagem em várias mudanças de configuração, desde o redimensionamento e mudança de orientação até a dobra e o desdobramento.

Outros recursos

Para saber mais sobre as mudanças de configuração e como elas são gerenciadas, consulte Gerenciar mudanças de configuração.

Gerenciamento de estado da RecyclerView

Três estrelas

A RecyclerView pode mostrar grandes quantidades de dados usando o mínimo de recursos gráficos. À medida que uma RecyclerView rola a lista de itens, ela reutiliza as instâncias de View dos itens que rolaram para fora da tela e cria novos itens conforme eles aparecem. No entanto, mudanças de configuração, como a rotação do dispositivo, podem redefinir o estado de uma RecyclerView, forçando os usuários a rolar novamente para a posição anterior na lista de itens da RecyclerView.

Práticas recomendadas

A RecyclerView precisa manter o estado, principalmente a posição de rolagem, e o estado dos elementos da lista durante todas as mudanças de configuração.

Componentes

Etapas

Resumo

Defina a política de restauração de estado da RecyclerView.Adapter para salvar a posição de rolagem da RecyclerView. Salve o estado dos itens da lista da RecyclerView. Adicione o estado desses itens ao adaptador da RecyclerView e restaure o estado quando eles estiverem vinculados a um ViewHolder.

1. Ativar a política de restauração de estado do Adapter

Ative a política de restauração de estado do adaptador da RecyclerView para que a posição de rolagem da RecyclerView seja mantida durante as mudanças de configuração. Adicione a especificação da política ao construtor do adaptador:

Kotlin

class MyAdapter() : RecyclerView.Adapter() {
    init {
        stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
    }
    ...
}

Java

class MyAdapter extends RecyclerView.Adapter {

    public Adapter() {
        setStateRestorationPolicy(StateRestorationPolicy.PREVENT_WHEN_EMPTY);
    }
    ...
}

2. Salvar o estado dos itens da lista com estado

Salve o estado de itens complexos da lista da RecyclerView, como aqueles que contêm elementos EditText. Por exemplo, se quiser salvar o estado de uma EditText, adicione um callback semelhante a um gerenciador onClick para capturar mudanças de texto. No callback, defina quais dados serão salvos:

Kotlin

input.addTextChangedListener(
    afterTextChanged = { text ->
        text?.let {
            // Save state here.
        }
    }
)

Java

input.addTextChangedListener(new TextWatcher() {
    
    ...

    @Override
    public void afterTextChanged(Editable s) {
        // Save state here.
    }
});

Declare o callback na Activity ou no Fragment. Use um ViewModel para armazenar o estado.

3. Adicionar o estado dos itens da lista ao Adapter

Adicione o estado dos itens da lista a RecyclerView.Adapter. Transmita o estado do item ao construtor do adaptador quando a Activity ou o Fragment do host forem criados:

Kotlin

val adapter = MyAdapter(items, viewModel.retrieveState())

Java

MyAdapter adapter = new MyAdapter(items, viewModel.retrieveState());

4. Recuperar o estado dos itens da lista no ViewHolder do adaptador

Na RecyclerView.Adapter, restaure o estado de um item ao vincular um ViewHolder a ele:

Kotlin

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    ...
    val item = items[position]
    val state = states.firstOrNull { it.item == item }

    if (state != null) {
        holder.restore(state)
    }
}

Java

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    ...
    Item item = items[position];
    Arrays.stream(states).filter(state -> state.item == item)
        .findFirst()
        .ifPresent(state -> holder.restore(state));
}

Resultados

A RecyclerView agora pode restaurar a posição de rolagem e o estado de cada item na lista da RecyclerView.

Outros recursos

Gerenciamento de teclado removível

Três estrelas

O suporte a teclados removíveis ajuda a maximizar a produtividade do usuário em dispositivos de tela grande. O Android aciona uma mudança de configuração sempre que um teclado é anexado ou removido de um dispositivo, o que pode causar uma perda de estado da interface. O app pode salvar e restaurar o estado, permitindo que o sistema processe a recriação de atividades ou restringir a recriação de atividades para mudanças de configuração do teclado. Em todos os casos, todos os dados relacionados ao teclado são armazenados em um objeto Configuration. Os campos keyboard e keyboardHidden do objeto de configuração contêm informações sobre o tipo de teclado e a disponibilidade dele.

Práticas recomendadas

Os apps otimizados para telas grandes oferecem suporte a todos os tipos de dispositivo de entrada, desde teclados de software e hardware até stylus, mouse, trackpad e outros dispositivos periféricos.

O suporte a teclados externos envolve mudanças de configuração, que podem ser gerenciadas de duas maneiras:

  1. Deixe o sistema recriar a atividade em execução no momento e você vai gerenciar o estado do app.
  2. Gerencie a mudança de configuração. A atividade não será recriada:
    • Declarar todos os valores de configuração relacionados ao teclado
    • Criar um gerenciador de alterações de configuração

Apps de produtividade, que geralmente exigem um bom controle da interface para entrada de texto e outras entradas, podem se beneficiar da abordagem do tipo "aprenda a fazer" para processar mudanças de configuração.

Em casos especiais, pode ser útil mudar o layout do app quando um teclado de hardware é anexado ou removido, por exemplo, para liberar mais espaço para ferramentas ou janelas de edição.

Como a única maneira confiável de detectar mudanças de configuração é substituir o método onConfigurationChanged() de uma visualização, é possível adicionar uma nova instância de visualização à atividade do app e responder usando o gerenciador onConfigurationChanged() da visualização a mudanças de configuração causadas pelo teclado conectado ou desanexado.

Componentes

  • android:configChanges: atributo do elemento <activity> do manifesto do app. Informa o sistema sobre mudanças de configuração que o app gerencia.
  • View#onConfigurationChanged(): método que reage à propagação de uma nova configuração de aplicativo.

Etapas

Resumo

Declare o atributo configChanges e adicione valores relacionados ao teclado. Adicione um View à hierarquia de visualização da atividade e detecte as mudanças de configuração.

1. Declarar o atributo configChanges

Atualize o elemento <activity> no manifesto do app adicionando os valores keyboard|keyboardHidden à lista de mudanças de configuração já gerenciadas:

<activity
      …
      android:configChanges="...|keyboard|keyboardHidden">

2. Adicionar uma visualização vazia à hierarquia

Declare uma nova visualização e adicione o código do gerenciador dentro do método onConfigurationChanged() da visualização:

Kotlin

val v = object : View(this) {
  override fun onConfigurationChanged(newConfig: Configuration?) {
    super.onConfigurationChanged(newConfig)
    // Handler code here.
  }
}

Java

View v = new View(this) {
    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // Handler code here.
    }
};

Resultados

Seu app agora responderá a um teclado externo sendo conectado ou removido sem recriar a atividade em execução no momento.

Outros recursos

Para saber como salvar o estado da interface do app durante mudanças de configuração, como anexo ou remoção do teclado, consulte Salvar estados da interface.