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.
safeArgs
para passar dados entre fragmentos.LiveData
e seus observadores.Room
, criar um objeto de acesso a dados (DAO) e definir entidades.RecyclerView
básico com um Adapter
, ViewHolder
e layout de item.RecyclerView
.GridLayoutManager
.RecyclerView
. Implemente um ouvinte de clique para navegar até uma vista de detalhes quando um item for clicado.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.
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.
sleepdetail
. layout
, copie o arquivo fragment_sleep_detail.xml
.navigation.xml
, que adiciona a navegação para o sleep_detail_fragment
.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>
res/values/strings
, adicione o seguinte recurso de string: <string name="close">Close</string>
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:
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.sleepdetail
, abra e inspecione o código do SleepDetailViewModel
. Este modelo de vistas leva a chave para um SleepNight
e um DAO no construtor. 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. getNightWithId()
retorna um LiveData<SleepNight>
e é definida no SleepDatabaseDao
(no database
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.sleepdetail
, abra e inspecione o código da SleepDetailViewModelFactory
.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.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.navigation.xml
. Para o sleep_tracker_fragment
, observe que a nova ação para o sleep_detail_fragment
.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?
SleepTrackerFragment
hospeda muitas vistas e, portanto, ouvir eventos de clique no nível do fragmento não dirá qual item foi clicado. Ele nem mesmo dirá se foi um item que foi clicado ou um dos outros elementos da IU.RecyclerView
, é difícil descobrir exatamente em qual item da lista o usuário clicou.ViewHolder
, pois representa um item da lista. 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?
Adapter
exibe itens de dados em vistas, para que você possa manipular cliques no adaptador. No entanto, o trabalho do adaptador é adaptar os dados para exibição, não tratar com a lógica do aplicativo. ViewModel
, pois o ViewModel
tem acesso aos dados e à lógica para determinar o que precisa acontecer em resposta ao clique. sleeptracker
, abra SleepNightAdapter.kt.SleepNightListener
.
class SleepNightListener() {
}
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() =
}
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) =
}
onClick()
faz, forneça um retorno de chamada clickListener
no construtor de SleepNightListener
e atribua-o a onClick()
. 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)
}
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" />
android:onClick
ao ConstraintLayout
. clickListener:onClick(sleep)
usando um lambda de vinculação de dados, conforme mostrado abaixo:android:onClick="@{() -> clickListener.onClick(sleep)}"
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()) {
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)
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. 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
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.
onCreateView()
, encontre a variável adapter
. Observe que ele mostra um erro, pois agora espera um parâmetro de ouvinte de clique. 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()
})
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.
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
.
SleepTrackerViewModel
, no final, defina a função de tratador de clique onSleepNightClicked()
.fun onSleepNightClicked(id: Long) {
}
onSleepNightClicked()
, acione a navegação definindo _navigateToSleepDetail
para o id
passado da noite de sono clicada.fun onSleepNightClicked(id: Long) {
_navigateToSleepDetail.value = id
}
_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
onSleepDetailNavigated()
e defina seu valor como null
. fun onSleepDetailNavigated() {
_navigateToSleepDetail.value = null
}
SleepNightListener
para mostrar um toast.val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
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)
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()
}
})
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.
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)
}
}
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:
onClick()
. 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.
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)
}
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: