RecyclerView doesn't display last item when multiple ViewHolders are used - android

I'm using androidx.recyclerview.widget.RecyclerView to display a list of items, separated by an other item as a "header" with some aggregated values.
When i put only one item in my list without adding the header, everything is ok and the item is displayed correctly. As soon as i add the header item, only the header is displayed and the one single item isn't shown.
When i add two items and the header, the header and one item are displayed. I don't know why the last item of my list is missing altough it exists in the adapters datasource.
My ListAdapter inherits from RecyclerView.Adapter<RecyclerView.ViewHolder> and uses two ViewHolders detected by a viewType property of my list items.
When loading the data, the onBindViewHolder method isn't called for the last item in my list, even tough the item is in the visible section of my screen.
Does anybody has a hint, why this happens?
class ListAdapter(val onClick: (position: Long) -> Unit,
val onLongClick: (Long) -> Unit,
val onShareClick: (id: Long?) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>(),
BindableAdapter<List<ListAdapterItem<*>>> {
var items: List<ListAdapterItem<*>> = emptyList()
private var actionMode: ActionMode? = null
var tracker: SelectionTracker<Long>? = null
init {
setHasStableIds(true)
}
override fun setData(data: List<ListAdapterItem<*>>) {
this.items = data // all items are set correctly here!!
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int {
return if (items.isEmpty()) EMPTY else items[position].viewType
}
override fun getItemCount(): Int {
return if (items.isEmpty()) 1 else items.filter { it.viewType == ITEM }.size
}
override fun getItemId(position: Int): Long = position.toLong()
fun getItem(position: Long): ListViewModel.ListItem = item[position.toInt()].value as ListViewModel.ListItem
fun setActionMode(actionMode: ActionMode?) {
this.actionMode = actionMode
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
EMPTY -> EmptyViewHolder(parent)
HEADER -> HistoryGroupHeaderViewHolder(parent)
else -> HistoryViewHolder(parent)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is HistoryViewHolder) {
val item = items[position].value as ListViewModel.ListItem
tracker?.let {
holder.bind(item, it.isSelected(position.toLong()))
}
holder.itemView.setOnClickListener {
onClick(position.toLong())
}
holder.itemView.findViewById<AppCompatImageView>(R.id.history_item_share)?.setOnClickListener {
onShareClick(item.id)
}
}
else if (holder is HistoryGroupHeaderViewHolder) {
val header = items[position].value as ListViewModel.ListSectionHeader
holder.bind(header)
}
}
class HistoryViewHolder(
private val parent: ViewGroup,
private val binding: at.app.databinding.ViewHistoryListItemBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.view_history_list_item,
parent,
false
)
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: ListViewModel.ListItem, isActivated: Boolean = false) {
binding.model = item
itemView.isActivated = isActivated
val imageView = itemView.findViewById<AppCompatImageView>(R.id.history_item_image)
if(itemView.isActivated) {
val parameter = imageView?.layoutParams as ConstraintLayout.LayoutParams
parameter.setMargins(
parent.context.resources.getDimension(R.dimen.spacing_small).toInt(),
parent.context.resources.getDimension(R.dimen.spacing_small).toInt(),
parent.context.resources.getDimension(R.dimen.spacing_small).toInt(),
parent.context.resources.getDimension(R.dimen.spacing_small).toInt()
)
imageView.layoutParams = parameter
} else {
val parameter = imageView?.layoutParams as ConstraintLayout.LayoutParams
parameter.setMargins(0,0,0,0)
imageView.layoutParams = parameter
}
}
fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
object : ItemDetailsLookup.ItemDetails<Long>() {
override fun getPosition(): Int = adapterPosition
override fun getSelectionKey(): Long? = itemId
}
}
class HistoryGroupHeaderViewHolder(
private val parent: ViewGroup,
private val binding: at.app.databinding.ViewHistoryListGroupHeaderItemBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.view_history_list_group_header_item,
parent,
false
)
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: ListViewModel.ListSectionHeader) {
binding.model = item
}
}
class EmptyViewHolder(
private val parent: ViewGroup, view: View = LayoutInflater.from(parent.context).inflate(
R.layout.view_history_empty_item,
parent,
false
)
) : RecyclerView.ViewHolder(view)
companion object {
const val EMPTY = 0
const val ITEM = 1
const val HEADER = 2
}
}
class MyItemDetailsLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup<Long>() {
private val log = LoggerFactory.getLogger(ListAdapter::class.java)
override fun getItemDetails(e: MotionEvent): ItemDetails<Long>? {
val view = recyclerView.findChildViewUnder(e.x, e.y)
if (view != null) {
return try {
if(recyclerView.getChildViewHolder(view) is ListAdapter.HistoryViewHolder) {
(recyclerView.getChildViewHolder(view) as ListAdapter.HistoryViewHolder)
.getItemDetails()
} else {
null
}
} catch (ex: Exception) {
log.error("Error on getItemDetails. ", ex)
null
}
}
return null
}
}
data class ListAdapterItem<out T>(val value: T, val viewType: Int)
And this is my layout:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="at.app.ui.viewmodel.ListViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="#+id/list_app_bar"
layout="#layout/layout_toolbar" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/history_recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/transparent"
android:scrollbars="vertical"
app:data="#{viewModel.items}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/list_app_bar" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

When i add two items and the header, the header and one item are
displayed.
problem is in your getItemCount method.
override fun getItemCount(): Int {
return if (items.isEmpty()) 1 else items.filter { it.viewType == ITEM }.size
}
If you want to show 1 header and 2 elements that means that there are must be 3 items in recyclerview, so getItemCount must return 3. But now it looks like getItemCount will return 2, thats why recycerlview doesn't even create third element.

Related

Nested vertical recyclerviews scroll lag

My use case model looks like:
data class CombinedModel(
val header: String?,
val contactsList: List<ContactModel>
)
I have to display this model as List < CombinedModel > in a recyclerview so it will be a header title with a list of items below it. My Xml layout for the parent recyclerview is simply a MaterialTextView and a RecyclerView. The child recyclerview contains the layout for the list of ContactModel.
The problem is when scrolling down the parent list, each time a child RecyclerView comes onto screen there is a noticeable stutter/lag as it draws the next ContactModel list. The goal here would be to get this working with a smooth buttery scroll and no lag.
class ParentAdapter
constructor(
private val interaction: ContactAdapter.Interaction? = null,
private var customisedColour: String?
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(),
Filterable {
private val viewPool = RecyclerView.RecycledViewPool()
private var list = emptyList<DirectoryCombinedModel>()
private var listAll = directoryList
private val listFilter = ListFilter()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ParentViewHolder(
RvParentBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
interaction = interaction
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ParentViewHolder -> {
list[position].let { holder.bind(it) }
}
}
}
override fun getItemCount(): Int {
return if (!list.isNullOrEmpty()) {
list.size
} else 0
}
fun updateList(updatedList: List<CombinedModel>) {
list = updatedList
listAll = updatedList
notifyDataSetChanged()
}
inner classParentViewHolder
constructor(
private val binding: RvParentBinding,
private val interaction: ContactAdapter.Interaction?
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: CombinedModel) = with(itemView) {
with(binding) {
headerTitle.text = item.header
val childLayoutManager = GridLayoutManager(
rv.context, 2
)
childLayoutManager.initialPrefetchItemCount = item.contactList.size
rv.apply {
layoutManager = childLayoutManager
adapter = ContactAdapter(
interaction = interaction,
customisedColour = customisedColour,
contacts = item.contactsList
)
setHasFixedSize(true)
setRecycledViewPool(viewPool)
}
}
}
}
PARENT XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mainContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="#+id/cvHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="#color/transparent"
app:cardCornerRadius="0dp"
app:cardElevation="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
android:id="#+id/headerTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="start"
android:paddingStart="#dimen/_8sdp"
android:paddingTop="#dimen/_18sdp"
android:paddingEnd="#dimen/_8sdp"
android:paddingBottom="#dimen/_12sdp"
android:text="#string/title"
android:textColor="#color/textColorPrimary"
android:textSize="#dimen/text_size_subHeading"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/cvHeader"
app:spanCount="2"
tools:listitem="#layout/rv_contact" />
</androidx.constraintlayout.widget.ConstraintLayout>
CHILD ADAPTER
class DirectoryContactAdapter
constructor(
private val interaction: Interaction? = null,
private val customisedColour: String?,
private var contacts: List<ContactModel>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var contactsAll = contacts
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return ContactViewHolder(
RvContactBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
interaction = interaction
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ContactViewHolder -> {
contacts[position].let { holder.bind(it) }
}
}
}
override fun getItemCount(): Int {
return if (!contacts.isNullOrEmpty()) {
contacts.size
} else 0
}
inner class ContactViewHolder
constructor(
private val binding: RvContactBinding,
private val interaction: Interaction?
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: ContactModel) = with(itemView) {
with(binding) {
setOnClickListener {
interaction?.onItemSelected(absoluteAdapterPosition, item)
}
name.text = item.name
role.text = item.title
Glide.with(contactImage.context)
.load(item.imageName)
.centerCrop()
.circleCrop()
.placeholder(R.drawable.ic_contact)
.error(R.drawable.ic_contact)
.transition(
DrawableTransitionOptions.withCrossFade()
).into(contactImage)
directoryImageBackground.setTintHex(customisedColour)
}
}
}
Solved it by following the approach layed out in this accepted answer: Divide elements on groups in RecyclerView or Grouping Recyclerview items ,say by date

Modify the number of columns dynamically in a RecyclerView and also the size of the images

I have been able to change the number of columns in a RecyclerView with the GridLayout dynamically 2-1, but in addition I need to change the height so that when it is 1 column the image size is larger than in the 2 column row.
I have seen that with StaggeredGridLayoutManager I can do that resizing but I can't figure out how to do both.
val gridLayoutManager=GridLayoutManager(requireContext(),2)
binding.recycler.layoutManager=gridLayoutManager
gridLayoutManager.spanSizeLookup=object:GridLayoutManager.SpanSizeLookup(){
override fun getSpanSize(position: Int): Int {
return if((position+1)%3==0){
2
}else{
1
}
}
Glide.with(binding.root)
.asBitmap()
.load(bindObject.image)
.apply(RequestOptions().centerCrop())
.apply(
RequestOptions()
.override(160, 160))
.into(binding.image!!)
}
class HomeAdapter(private val items: MutableList, val viewModel: HomeViewModel): GlobalAdapter(items) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GlobalViewHolder {
val inflater = LayoutInflater.from(parent.context)
return HomeViewHolder(DataBindingUtil.inflate(inflater, R.layout.item_news2, parent, false),
viewModel)
}
override fun onBindViewHolder(holder: GlobalViewHolder<News>, position: Int) {
holder.bind(items[position])
}
fun addItems(items: List<News>?) {
if (items != null) {
this.items.addAll(items)
notifyDataSetChanged()
}
}
}
You could define multiple viewTypes in your RecyclerView.Adapter and assign them a custom height based on your needs.
Define two layout types, for example item_small and item_large.
item_small represents the smaller image:
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/imageViewSmall"
android:layout_width="match_parent"
android:layout_height="100dp" <!-- Smaller size -->
/>
item_large represents the bigger image:
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/imageViewLarge"
android:layout_width="match_parent"
android:layout_height="250dp" <!-- Bigger size -->
/>
You can then use the getItemViewType method to define the type of view that should be displayed based on your condition ((position+1)%3==0):
private const val ITEM_VIEW_TYPE_SMALL = 0
private const val ITEM_VIEW_TYPE_LARGE = 1
class HomeAdapter(
private val items: MutableList,
val viewModel: HomeViewModel
): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun getItemViewType(position: Int): Int = if ((position+1) % 3 == 0) {
// 2 column item, return the small image
ITEM_VIEW_TYPE_SMALL
} else {
// 1 column item, return the large image
ITEM_VIEW_TYPE_LARGE
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
if (viewType == ITEM_VIEW_TYPE_SMALL) return SmallViewHolder(ItemSmallBinding.inflate(inflater, parent, false))
return LargeViewHolder(ItemLargeBinding.inflate(inflater, parent, false))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(...) // Pass the image url here
}
fun addItems(items: List<News>?) {
if (items != null) {
this.items.addAll(items)
notifyDataSetChanged()
}
}
// Small image view holder
class SmallViewHolder(private val binding: ItemSmallBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(image: String) {
// Set the image in imageViewSmall
}
}
// Large image view holder
class LargeViewHolder(private val binding: ItemLargeBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(image: String) {
// Set the image in imageViewLarge
}
}
}

RecyclerView items not displaying

The title says, I'm trying to load items into a recyclerview but the items doesn't displaying.
I'm getting the items from the api, using retrofit and mutablelivedata, I'm getting the items right, and the adapter gets the items (there are 3 items, and the adapter gets the 3 items), but the items doesn't display on the UI.
Here is my code:
Adapter.java
class EstablecimientosAdapter : ListAdapter<EstablecimientoModel, EstablecimientosViewHolder>(
DIFF_CALLBACK
) {
companion object {
val DIFF_CALLBACK: DiffUtil.ItemCallback<EstablecimientoModel> =
object : DiffUtil.ItemCallback<EstablecimientoModel>() {
override fun areItemsTheSame(
oldItem: EstablecimientoModel,
newItem: EstablecimientoModel
): Boolean {
return oldItem.hash == newItem.hash
}
override fun areContentsTheSame(
oldItem: EstablecimientoModel,
newItem: EstablecimientoModel
): Boolean {
return oldItem.nombre == newItem.nombre
}
}
}
private val mEstablecimientos: MutableList<EstablecimientoModel> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EstablecimientosViewHolder {
val binding = RowEstablecimientoBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return EstablecimientosViewHolder(binding)
}
override fun onBindViewHolder(holder: EstablecimientosViewHolder, position: Int) {
val establecimiento = getItem(position)
holder.bindItem(establecimiento!!)
}
override fun getItemCount(): Int {
return mEstablecimientos.size
}
fun addMoreEstablecimientos(newEstablecimientos: List<EstablecimientoModel>) {
mEstablecimientos.addAll(newEstablecimientos)
submitList(mEstablecimientos)
}
class EstablecimientosViewHolder(val binding: RowEstablecimientoBinding) :
RecyclerView.ViewHolder(
binding.root
) {
fun bindItem(establecimiento: EstablecimientoModel) {
binding.setVariable(BR.establecimiento, establecimiento)
}
}
}
recycler_view.xml
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvEstablecimientos"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="#layout/row_establecimiento" />
Fragment.java
val cvGoBackEstablecimientos = binding!!.cvGoBackEstablecimientos
val rvEstablecimientos = binding!!.rvEstablecimientos
val adapter = EstablecimientosAdapter()
rvEstablecimientos.setHasFixedSize(true)
rvEstablecimientos.adapter = adapter
cvGoBackEstablecimientos.setOnClickListener { requireActivity().onBackPressed() }
viewModel?.getNearFreeEstablecimientos(2.0, 2.0)
?.observe(viewLifecycleOwner) { t -> adapter.addMoreEstablecimientos(t!!) }
It seems that I had some problems with the layout, I removed a CoordinatorLayout and it works now.

Change background color of selectedItem in recyclerView android databinding

How can I change the backgroudColor ofselectedItem in recyclerView adapter when I use android databinding?
this is my Adapte,and class CategoyItemClickListener is implemened for handling item clicks :
class ProgramCatAdapter(
val mContext: Context,
val mData: MutableList<CategoryResponse>,
val clickListener: CategoyItemClickListener
) : RecyclerView.Adapter<ProgramCatAdapter.CategoryViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): CategoryViewHolder {
return CategoryViewHolder.from(parent)
}
override fun getItemCount(): Int {
return mData.size
}
override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
holder.bind(
mData[position],
clickListener,
position
)
}
fun getItem(position: Int): CategoryResponse = mData[position]
fun getPosition(item: CategoryResponse): Int = mData.indexOf(item)
class CategoryViewHolder private constructor(val binding: ProgramCatHorizontalBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(
item: CategoryResponse,
clickListener: CategoyItemClickListener,
position: Int
) {
itemView.isActivated = isSelected
binding.item = item
binding.position = position
binding.clickListener = clickListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): CategoryViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ProgramCatHorizontalBinding.inflate(layoutInflater, parent, false)
return CategoryViewHolder(binding)
}
}
}
}
class CategoyItemClickListener(val clickListener: (item: CategoryResponse) -> Unit) {
fun onClick(item: CategoryResponse) {
clickListener(item)
}
}
And this code is for binding adapter to recyclerView :
private fun bindCategories(cats: MutableList<CategoryResponse>?) {
programCatAdapter = ProgramCatAdapter(mContext!!,
cats!!, CategoyItemClickListener {
viewModel.setSelectedCat(it)
})
binding.catRecycler.layoutManager =
LinearLayoutManager(activity!!, LinearLayoutManager.HORIZONTAL, false)
binding.catRecycler.adapter = programCatAdapter
}
How can I have access to the position of selected Item from CategoyItemClickListener and change the bgColor of that item?
Here an example:
This video might give you an overview: https://youtu.be/g8GDLOMt600 (You are not going to find a solution for this question in this video. It just gives you a nice overview about data binding, RecyclerView and ClickListener)
You need the following files:
YourFragment, fragment_your.xml, list_item
class YourFragment : Fragment(){
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: FragmentYoutFragmentBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_your, container, false
)
binding.lifecycleOwner = this
// Very important! Otherwise the layout of the recycler view wont work
binding.yourList.layoutManager = LinearLayoutManager(this.context)
// Adapter for the RecyclerView in order to show all items
val adapter = YourAdapter(YourListener{youtItemObeject: YourItemObject, view:
View ->
// THIS IS THE SOLUTION
// change selected item image, if user selects this item in the list
view.ok_image.setImageResource(R.drawable.ok_green)
})
}
}
in list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="your_object"
type="your_package_name.your_folder.YourObject" />
<variable
name="clickListener"
type="your_package_name.your_folder.YourListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/item_margin_horizontal"
android:layout_marginTop="#dimen/item_margin_vertical"
android:layout_marginEnd="#dimen/item_margin_horizontal"
android:layout_marginBottom="#dimen/item_margin_vertical"
android:onClick="#{(thisView) -> clickListener.onClick(your_object, thisView)}">
<ImageView
android:id="#+id/ok_image"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And add YourListener class into the adapter-File
class YourAdapter(val clickListener: YourListener) :
ListAdapter<YourObject, YourAdapter.ViewHolder>(YourDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position)!!, clickListener)
}
class ViewHolder private constructor(val binding: ListItemYourObjectBinding) :
RecyclerView.ViewHolder(binding.root) {
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding =
ListItemYourObjectBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
fun bind(item: YourObject, clickListener: YourListener) {
binding.yourObject = item
binding.clickListener = clickListener
// for example
binding.okImage.setImageRessouce(R.drawable.ok_gray)
binding.executePendingBindings()
}
}
}
/**
* Callback for calculating the diff between two non-null items in a list.
*
* Used by ListAdapter to calculate the minimum number of changes between and old list
* and a new list that's been passed to `submitList`.
*/
class YourObjectDiffCallback : DiffUtil.ItemCallback<YourObject>() {
override fun areItemsTheSame(oldItem: YourObject, newItem: YourObject): Boolean {
return oldItem.yourObjectValue == newItem.yourObjectValue
}
override fun areContentsTheSame(oldItem: YourObject, newItem: YourObject): Boolean {
return oldItem == newItem
}
}
/**
* Listener for your list items
*/
class YourListener(val clickListener: (yourObject: YourObject,
view: View) -> Unit) {
fun onClick(yourObject: YourObject, view: View) =
clickListener(yourObject, view)
}

setOnLongClickListener in android with kotlin

How can I use setOnItemClickListner in each item in my ListView?
my xml :
<ListView
android:id="#+id/tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
this my adapter class
inner class mo3d1Adapter : BaseAdapter {
override fun getItemId(p0: Int): Long {
return p0.toLong()
}
override fun getCount(): Int {
return listOfmo3d.size
}
var listOfMkabala = ArrayList<MeetingDetails>()
var context: Context? = null
constructor(context: Context, listOfMkabaln: ArrayList<MeetingDetails>) : super() {
this.listOfMkabala = listOfMkabaln
this.context = context
}
override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {
val mo3d = listOfmo3d[p0]
var inflatormo3d = context!!.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
var myViewmo3d = inflatormo3d.inflate(R.layout.fragment_item, null)
lvMo3d.onItemClickListener = AdapterView.OnItemClickListener { adapterView, view, i, l ->
Toast.makeText(context, " TEST STACK ", Toast.LENGTH_LONG).show()
}
myViewmo3d.meeting_name.text = mo3d.name1!!
myViewmo3d.meeting_date.text = mo3d.date.toString()!!
myViewmo3d.attendance_number.text = mo3d.n2.toString()!!
return myViewmo3d
}
override fun getItem(p0: Int): Any {
return listOfmo3d[p0]
}
}
I want listener for each item in my ListView
And when I used this method setOnClickListener in adapter it's not working, where can I use?
Try this in your activity class
lv.setOnItemClickListener { parent, view, position, id ->
Toast.makeText(this, "Position Clicked:"+" "+position,Toast.LENGTH_SHORT).show()
}
Although a little quirky this works fine for me.
latestMessagesAdapter.setOnItemLongClickListener { item, view ->
val row = item as LatestMessageRow
return#setOnItemLongClickListener(true)
}
First of all I would like to tell that it is RecyclerView rather than ListView. You can find plenty information why to do in such. For example you can read it hear :
RecyclerView vs. ListView
Regarding your question how to do it in correct way with RecyclerView.
Insert dependencies with RecyclerView, they are now in support library in Kotlin.
implementation "com.android.support:appcompat-v7:25.4.0"
First change your ListView with RecyclerView in xml layout like this:
<android.support.v7.widget.RecyclerView
android:id="#+id/accountList"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Create Adapter for RecyclerView:
class AccountListAdapter(val accountList: AccountList, val itemListener: (Account) -> Unit) :
RecyclerView.Adapter<AccountListAdapter.ViewHolder>(){
override fun getItemCount(): Int = accountList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) =
holder.bind(accountList[position])
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder{
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_account, parent, false)
return ViewHolder(view, itemListener)
}
class ViewHolder(itemView: View, val itemClick: (Account) -> Unit): RecyclerView.ViewHolder(itemView){
fun bind(account : Account){
with(account){
itemView.accountName.text = title
itemView.setOnClickListener{ itemClick(this)}
}
}
}
}
item_account.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/accountName"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
Models (in Kotlin you can put them in one file and name for example AccountModels.kt) :
data class AccountList(val accounts: List<Account>){
val size : Int
get() = accounts.size
operator fun get(position: Int) = accounts[position]
}
data class Account(val id : Long, val title : String, val balance : Int, val defCurrency: Int)
In Fragment/Activity connect your Adapter to RecyclerView:
override fun onStart() {
super.onStart()
setupAdapter()
}
fun setupAdapter(){
Log.d(TAG, "updating ui..")
val account1 = Account(1,"Credit", 1000, 2)
val account2 = Account(2, "Debit", 500, 2)
val account3 = Account(3, "Cash", 7000, 2)
val accounts : List<Account> = listOf(account1, account2, account3)
val adapter = AccountListAdapter(AccountList(accounts)){
val title = it.title
Log.d(TAG, "$title clicked")
}
accountList.layoutManager = LinearLayoutManager(activity)
accountList.adapter = adapter
}
That is all. Everything should work now. Hope it helps.

Categories

Resources