Introdução

A maioria dos aplicativos que usam listas e grades que exibem itens permitem que os usuários interajam com os itens. Tocando em um item de uma lista e ver os detalhes do item é um caso de uso muito comum para esse tipo de interação. Para conseguir isso, você pode adicionar ouvintes de clique que respondem aos toques do usuário nos itens, mostrando uma vista de detalhes.

Neste tutorial, você adiciona interação ao seu RecyclerView, com base em uma versão estendida do aplicativo sleep-tracker da série anterior de tutoriais.

O que você já deveria saber

O que aprenderá

O que fará

O aplicativo sleep-tracker inicial possui duas telas, representadas por fragmentos, conforme mostrado na figura abaixo.

A primeira tela, mostrada à esquerda, possui botões para iniciar e parar o rastreamento. A tela mostra alguns dos dados de sono do usuário. O botão Clear exclui permanentemente todos os dados que o aplicativo coletou para o usuário. A segunda tela, mostrada à direita, é para selecionar uma classificação de qualidade do sono.

Este aplicativo usa uma arquitetura simplificada com um controlador de IU, modelo de vistas e LiveData e um banco de dados Room para persistir os dados de sono.

Neste tutorial, você adiciona a capacidade de responder quando um usuário toca em um item na grade, o que abre uma tela de detalhes como a mostrada abaixo. O código para esta tela (fragmento, modelo de vistas e navegação) é fornecido com o aplicativo inicial e você implementará o mecanismo de tratamento de cliques.

Etapa 1: Obtenha o aplicativo inicial

  1. Baixe o código RecyclerViewClickHandler-Starter do GitHub e abra o projeto no Android Studio.
  2. Construa e execute o aplicativo starter sleep-tracker.

[Opcional] Atualize seu aplicativo se quiser usar o aplicativo do tutorial anterior

Se você for trabalhar no aplicativo inicial fornecido no GitHub para este tutorial, pule para a próxima etapa.

Se você deseja continuar usando seu próprio aplicativo sleep-tracker que você construiu no tutorial anterior, siga as instruções abaixo para atualizar seu aplicativo existente para que tenha o código para o fragmento da tela de detalhes.

  1. Mesmo se você estiver continuando com seu aplicativo existente, obtenha o código RecyclerViewClickHandler-Starter do GitHub para que possa copiar os arquivos.
  2. Copie todos os arquivos do pacote sleepdetail.
  3. Na pasta layout, copie o arquivo fragment_sleep_detail.xml.
  4. Copie o conteúdo atualizado de navigation.xml, que adiciona a navegação para o sleep_detail_fragment.
  5. No pacote database, no SleepDatabaseDao, adicione o novo método getNightWithId():
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
fun getNightWithId(key: Long): LiveData<SleepNight>
  1. Em res/values/strings, adicione o seguinte recurso de string:
<string name="close">Close</string>
  1. Limpe e reconstrua seu aplicativo para atualizar a vinculação de dados.

Etapa 2: Inspecione o código da tela de detalhes do sono

Neste tutorial, você implementa um tratador de cliques que navega até um fragmento que mostra detalhes sobre a noite de sono clicada. Seu código inicial já contém o fragmento e o grafo de navegação para este SleepDetailFragment, pois é um pouco de código e fragmentos e navegação não fazem parte deste tutorial. Familiarize-se com o seguinte código:

  1. Em seu aplicativo, encontre o pacote sleepdetail. Este pacote contém o fragmento, o modelo de vistas e a fábrica de modelos de vistas para um fragmento que exibe detalhes para uma noite de sono.

  2. No pacote sleepdetail, abra e inspecione o código do SleepDetailViewModel. Este modelo de vistas leva a chave para um SleepNight e um DAO no construtor.

    O corpo da classe tem código para obter o SleepNight para a chave fornecida e a variável navigateToSleepTracker para controlar a navegação de volta para o SleepTrackerFragment quando o botão Close é pressionado.

    A função getNightWithId() retorna um LiveData<SleepNight> e é definida no SleepDatabaseDao (no database pacote).

  3. No pacote sleepdetail, abra e inspecione o código do SleepDetailFragment. Observe a configuração para vinculação de dados, o modelo de vistas e o observador para navegação.

  4. No pacote sleepdetail, abra e inspecione o código da SleepDetailViewModelFactory.

  5. Na pasta de layout, inspecione fragment_sleep_detail.xml. Observe a variável sleepDetailViewModel definida na etiqueta <data> para obter os dados a serem exibidos em cada vista do modelo de vista.

    O layout contém um ConstraintLayout que contém um ImageView para a qualidade do sono, um TextView para uma classificação de qualidade, um TextView para a duração do sono e um Button para fechar o fragmento de detalhe.

  6. Abra o arquivo navigation.xml. Para o sleep_tracker_fragment, observe que a nova ação para o sleep_detail_fragment.

    A nova ação, action_sleep_tracker_fragment_to_sleepDetailFragment, é a navegação do fragmento do rasthreador de sono para a tela de detalhes.

Nesta tarefa, você atualiza o RecyclerView para responder aos toques do usuário, mostrando uma tela de detalhes para o item tocado.

Recebendo cliques e gerenciá-los é uma tarefa de duas partes: Primeiro, você precisa ouvir e receber o clique e determinar qual item foi clicado. Então, você precisa responder ao clique com uma ação.

Então, qual é o melhor lugar para adicionar um ouvinte de cliques para este aplicativo?

Embora o ViewHolder seja um ótimo lugar para ouvir os cliques, geralmente não é o lugar certo para tratar com eles. Então, qual é o melhor local para tratar com os cliques?

Etapa 1: Crie um ouvinte de clique e acione-o a partir do layout do item

  1. Na pasta sleeptracker, abra SleepNightAdapter.kt.
  2. No final do arquivo, no nível superior, crie uma classe de ouvinte, SleepNightListener.
class SleepNightListener() {
    
}
  1. Dentro da classe SleepNightListener, adicione uma função onClick(). Quando a vista que exibe um item da lista é clicada, a vista chama esta função onClick(). (Você definirá a propriedade android:onClick da vista posteriormente para esta função).
class SleepNightListener() {
    fun onClick() = 
}
  1. Adicione um argumento de função night do tipo SleepNight a onClick(). A vista sabe qual item está exibindo e que informações precisam ser repassadas para tratar com o clique.
class SleepNightListener() {
    fun onClick(night: SleepNight) = 
}
  1. Para definir o que onClick() faz, forneça um retorno de chamada clickListener no construtor de SleepNightListener e atribua-o a onClick().

    Atribua ao lambda que manipula o clique um nome, clickListener, ajuda a controlá-lo à medida que é passado entre as classes. O retorno de chamada clickListener somente precisa do night.nightId para acessar os dados do banco de dados. Sua classe finalizada SleepNightListener deve ser semelhante ao código abaixo.
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
   fun onClick(night: SleepNight) = clickListener(night.nightId)
}
  1. Abra list_item_sleep_night.xml.
  2. Dentro do bloco data, adicione uma nova variável para tornar a classe SleepNightListener disponível por meio da vinculação de dados. Provenha ao novo <variable> um name de clickListener. Defina o type para o nome qualificado da classe com.example.android.trackmysleepquality.sleeptracker.SleepNightListener, conforme mostrado abaixo. Agora você pode acessar a função onClick() em SleepNightListener a partir deste layout.
<variable
            name="clickListener"
            type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />
  1. Para ouvir cliques em qualquer parte deste item da lista, adicione o atributo android:onClick ao ConstraintLayout.

    Defina o atributo para clickListener:onClick(sleep) usando um lambda de vinculação de dados, conforme mostrado abaixo:
android:onClick="@{() -> clickListener.onClick(sleep)}"

Etapa 2: Passe o ouvinte de clique para o recipiente da vista e o objeto de vinculação

  1. Abra SleepNightAdapter.kt.
  2. Modifique o construtor da classe SleepNightAdapter para receber um val clickListener: SleepNightListener. Quando o adaptador vincula o ViewHolder, ele precisará fornecer este ouvinte de clique.
class SleepNightAdapter(val clickListener: SleepNightListener):
       ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
  1. Em onBindViewHolder(), atualize a chamada para holder.bind() para também passar o ouvinte de clique para o ViewHolder. Você obterá um erro do compilador porque adicionou um parâmetro à chamada de função.
holder.bind(getItem(position)!!, clickListener)
  1. Adicione o parâmetro clickListener a bind(). Para fazer isso, coloque o cursor sobre o erro e pressione Alt+Enter (Windows) ou Option+Enter (Mac) no erro para, como mostrado na imagem abaixo.

  1. Dentro da classe ViewHolder, dentro da função bind(), atribua o ouvinte de clique ao objeto binding. Você vê um erro, pois precisa atualizar o objeto de vinculação.
binding.clickListener = clickListener
  1. Para atualizar a vinculação de dados, Clean e Rebuild seu projeto. (Você pode precisar invatratar caches também). Portanto, você pegou um ouvinte de clique do construtor do adaptador e o passou para o recipiente de vistas e para o objeto de vinculação.

Etapa 3: Exiba um toast quando um item for tocado

Agora você tem o código para capturar um clique, mas não implementou o que acontece quando um item da lista é tocado. A resposta mais simples é exibir um toast mostrando o nightId quando um item é clicado. Isso verifica se quando um item da lista é clicado, o nightId correto é capturado e transmitido.

  1. Abra SleepTrackerFragment.kt.
  2. Em onCreateView(), encontre a variável adapter. Observe que ele mostra um erro, pois agora espera um parâmetro de ouvinte de clique.
  3. Defina um ouvinte de clique passando um lambda para o SleepNightAdapter. Este lambda simples exibe apenas um toast mostrando o nightId, conforme mostrado abaixo. Você terá que importar Toast. Abaixo está a definição atualizada completa.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
   Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
  1. Execute o aplicativo, toque nos itens e verifique se eles exibem um toast com o nightId correto. Como os itens possuem valores crescentes de nightId e o aplicativo exibe a noite mais recente primeiro, o item com o nightId mais baixo está no final da lista.

Nesta tarefa, você altera o comportamento quando um item no RecyclerView é clicado, de forma que, em vez de mostrar um toast, o aplicativo navegue até um fragmento de detalhe que mostra mais informações sobre a noite clicada.

Etapa 1: Navegue com clique

Nesta etapa, em vez de apenas exibir um toast, você altera o ouvinte de clique lambda em onCreateView() do SleepTrackerFragment para passar o nightId para o SleepTrackerViewModel e acionar a navegação para o SleepDetailFragment.

Defina a função do gerenciador de cliques:

  1. Abra SleepTrackerViewModel.kt.
  2. Dentro de SleepTrackerViewModel, no final, defina a função de tratador de clique onSleepNightClicked().
fun onSleepNightClicked(id: Long) {

}
  1. Dentro de onSleepNightClicked(), acione a navegação definindo _navigateToSleepDetail para o id passado da noite de sono clicada.
fun onSleepNightClicked(id: Long) {
   _navigateToSleepDetail.value = id
}
  1. Implemente _navigateToSleepDetail. Como você fez antes, defina um private MutableLiveData privado para o estado de navegação. E um val publicável para acompanhar.
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
   get() = _navigateToSleepDetail
  1. Defina o método a ser chamado depois que o aplicativo terminar de navegar. Chame-o de onSleepDetailNavigated() e defina seu valor como null.
fun onSleepDetailNavigated() {
    _navigateToSleepDetail.value = null
}

Adicione o código para chamar o gerenciador de cliques:

  1. Abra SleepTrackerFragment.kt e role para baixo até o código que cria o adaptador e define SleepNightListener para mostrar um toast.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
   Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
  1. Adicione o seguinte código abaixo da notificação para chamar um tratador de clique, onSleepNighClicked(), em sleepTrackerViewModel quando um item é tocado. Passe o nightId, para que o modelo de vistas saiba qual noite de sono obter. Isso o deixa com um erro, pois você não definiu onSleepNightClicked() ainda. Você pode manter, comentar ou excluir a notificação, como desejar.
sleepTrackerViewModel.onSleepNightClicked(nightId)

Adicione o código para observar os cliques:

  1. Abra SleepTrackerFragment.kt.
  2. Em onCreateView(), logo acima da declaração do manager, adicione o código para observar o novo navigateToSleepDetail LiveData. Quando navigateToSleepDetail mudar, navegue para SleepDetailFragment, passando a night, então chame onSleepDetailNavigated() depois. Como você já fez isso em um tutorial anterior, aqui está o código:
sleepTrackerViewModel.navigateToSleepDetail.observe(this, Observer { night ->
            night?.let {
              this.findNavController().navigate(
                        SleepTrackerFragmentDirections
                                .actionSleepTrackerFragmentToSleepDetailFragment(night))
               sleepTrackerViewModel.onSleepDetailNavigated()
            }
        })
  1. Execute seu código, clique em um item e ... o aplicativo trava.

Manipule valores nulos nos adaptadores de vinculação:

  1. Execute o aplicativo novamente, no modo de depuração. Toque em um item e filtre os registros para mostrar os erros. Ele mostrará um rastreamento de pilha incluindo algo como o que está abaixo.
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter item

Infelizmente, o rastreamento de pilha não deixa claro onde esse erro é acionado. Uma desvantagem da vinculação de dados é que ela pode dificultar a depuração do código. O aplicativo falha quando você clica em um item, e o único código novo é para tratar com o clique.

No entanto, acontece que, com este novo mecanismo de tratamento de cliques, agora é possível que os adaptadores de vinculação sejam chamados com um valor null para item. Em particular, quando o aplicativo é iniciado, o LiveData começa como null, então você precisa adicionar verificações de nulo a cada um dos adaptadores.

  1. Em BindingUtils.kt, para cada um dos adaptadores de vinculação, altere o tipo do argumento item para anulável e envolva o corpo com item?.let{...}. Por exemplo, seu adaptador para sleepQualityString será semelhante a este. Troque os outros adaptadores da mesma forma.
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
   item?.let {
       text = convertNumericQualityToString(item.sleepQuality, context.resources)
   }
}
  1. Execute seu aplicativo. Toque em um item para abrir uma exibição de detalhes.

Projeto Android Studio: RecyclerViewClickHandler.

Para fazer os itens em um RecyclerView responderem a cliques, anexe ouvintes de clique para listar itens em ViewHolder e manipular cliques em ViewModel.

Para fazer com que os itens em um RecyclerView respondam aos cliques, você precisa fazer o seguinte:

class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
   fun onClick(night: SleepNight) = clickListener(night.nightId)
}
android:onClick="@{() -> clickListener.onClick(sleep)}"
class SleepNightAdapter(val clickListener: SleepNightListener):
       ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()
holder.bind(getItem(position)!!, clickListener)
binding.clickListener = clickListener
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
      sleepTrackerViewModel.onSleepNightClicked(nightId)
})

Documentação para desenvolvimento em Android:

Esta seção lista as possíveis tarefas de casa para os alunos que estão trabalhando neste tutorial como parte de um curso ministrado por um instrutor.

Responda a essas perguntas

Pergunta 1

Suponha que seu aplicativo contenha um RecyclerView que exibe itens em uma lista de compras. Seu aplicativo também define uma classe de ouvinte de cliques:

class ShoppingListItemListener(val clickListener: (itemId: Long) -> Unit) {
    fun onClick(cartItem: CartItem) = clickListener(cartItem.itemId)
}

Como você torna o ShoppingListItemListener disponível para vinculação de dados? Selecione um.

▢ No arquivo de layout que contém o RecyclerView que exibe a lista de compras, adicione uma variável <data> para ShoppingListItemListener.

▢ No arquivo de layout que define o layout para uma única linha na lista de compras, adicione uma variável <data> para ShoppingListItemListener.

▢ Na classe ShoppingListItemListener, adicione uma função para ativar a vinculação de dados:

fun onBinding (cartItem: CartItem) {dataBindingEnable(true)}

▢ Na classe ShoppingListItemListener, dentro da função onClick(), adicione uma chamada para habilitar a vinculação de dados:

fun onClick(cartItem: CartItem) = { 
    clickListener(cartItem.itemId)
    dataBindingEnable(true)
}

Pergunta 2

Onde você adiciona o atributo android:onClick para fazer os itens em um RecyclerView responderem aos cliques? Selecione tudo que se aplica.

▢ No arquivo de layout que exibe o RecyclerView, adicione-o a <androidx.recyclerview.widget.RecyclerView>

▢ Adicione-o ao arquivo de layout para um item na linha. Se você deseja que todo o item seja clicável, adicione-o à vista pai que contém os itens na linha.

▢ Adicione-o ao arquivo de layout para um item na linha. Se desejar que um único TextView no item seja clicável, adicione-o ao <TextView>.

▢ Sempre adicione-o ao arquivo de layout para MainActivity.

Comece a próxima lição: 07.5: Cabeçalhos em RecyclerView