I'm trying to scroll to a recycler view item if the item is expanded and/or clicked. I know that binding.recyclerview.layoutManager.scrollToPosition() can achieve this but I can't call the recycler view in the adapter class. Can I pass a boolean and position then say its true when the card item is clicked, then pass that to the main activity somehow..? I'm still very much a beginner so steps to do achieve this with best practices would really help me out. :)
EDIT*
So we got the above issue figured out. Now I am having a new issue where the item is scrolled to when clicked, but the expanded description text that appears after clicking is not scrolled into view. Lol ugh. Would really appreciate any help with this.
Hope you are having a great day!
class MainAdapter: RecyclerView.Adapter<MainViewHolder>() {
var movies = mutableListOf<Model>()
#SuppressLint("NotifyDataSetChanged")
fun setMovieList(movies: List<Model>) {
this.movies = movies.toMutableList()
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = AdapterMovieBinding.inflate(inflater, parent, false)
return MainViewHolder(binding)
}
#SuppressLint("NotifyDataSetChanged")
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
val movie = movies[position]
holder.binding.name.text = movie.name
holder.binding.category.text = movie.category
holder.binding.description.text = movie.desc
Glide.with(holder.itemView.context).load(movie.imageUrl).into(holder.binding.imageview)
holder.binding.expandedView.visibility = if (movie.expand) View.VISIBLE else View.GONE
holder.binding.cardLayout.setOnClickListener {
movie.expand = !movie.expand
// scroll to expanded item
notifyDataSetChanged()
}
}
override fun getItemCount(): Int {
return movies.size
}
}
class MainViewHolder(val binding: AdapterMovieBinding) :
RecyclerView.ViewHolder(binding.root)
Related
I have a RecyclerView with a checkbox for every item. When checkbox pressed it is moved to the bottom of the list. When i check any item besides the first one everything is OK but if i check the first one, it auto-scroll to the new item location.
I redesigned the adapter using a recent codelab but still the same issue
class ItemsAdapter(val clickListener: ItemAdapterListener):
ListAdapter<UiQueryItem<Item>, ItemsAdapter.ViewHolder>(asyncDifferConfig) {
companion object {
private val diffCallback = object : UiQueryItemDiffCallback<Item>() {}
private val executors = AppExecutors()
private val asyncDifferConfig =
AsyncDifferConfig.Builder<UiQueryItem<Item>>(diffCallback)
.setBackgroundThreadExecutor(executors.cpuExecutorService)
.build()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item,clickListener)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
class ViewHolder private constructor(val binding: ListItemBinding): RecyclerView.ViewHolder(binding.root){
fun bind(item: UiQueryItem<Item>, clickListener: ItemAdapterListener) {
binding.item = item.item
binding.clickListener = clickListener
binding.isSelected = item.isFlag1
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
I think i found a workaround.
I noticed the next github issue solution:
Automatic scrolling when new data set #224
It did not work in my case but after tweaking a bit i found an answer. Please note that it is only a workaround and might make some bugs if you are trying to implement your own auto-scroll solution. The Workaround simply checkes if an item changed position from first place and if so, gets back to first position
Also note that #elihart is suggesting other solutions (check the link)
private val dataObserver = object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
if (fromPosition == 0) {
binding.itemRv.scrollToPosition(0)
}
}
}
override fun onResume() {
super.onResume()
adapter.registerAdapterDataObserver(dataObserver)
}
override fun onPause() {
super.onPause()
adapter.unregisterAdapterDataObserver(dataObserver)
}
I have a recyclerview with a list of items , when i check an item and scroll until the checked checkbox disppear , and scroll again to it , the checkbox become unchecked
this is my adapter code
class ContactsAdapter(var list:ArrayList<contact>, private val listener: (contact) -> Unit):RecyclerView.Adapter<ContactsAdapter.viewHolder>() {
inner class viewHolder(view:View):RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): viewHolder {
var view = LayoutInflater.from(parent.context).inflate(R.layout.item_ui, parent, false)
return viewHolder(view)
}
override fun onBindViewHolder(holder: viewHolder, position: Int) {
var itemPosition = list[position]
holder.itemView.apply {
contact_name.text=itemPosition.contactName
contact_number.text=itemPosition.contactNumber[0]
check_number.setOnClickListener {
listener(itemPosition)
}
check_number.isChecked = itemPosition.checked
}
}
override fun getItemCount(): Int {
return list.size
}
}
A RecyclerView recycles its rows. Your setOnClickListener() needs to do something to hold onto which items are checked and unchecked, and your onBindViewHolder() needs to update the CheckBox when it binds an item. It seems like the second part is implemented, but perhaps not the first part. You may need to update itemPosition.checked in your lambda for setOnClickListener().
I'm trying to put item which consists of title and comment on screen. (wanna make notes App)
So I used RecyclerView to show it can be updated when I touch.
I was confused about catching some grammer errors related to ViewBinding on Activity, Fragment and Adapter.
Many QNAs in here are helpful to solve grammer errors. but if I touched item, it doesn't work.
Code is here:
class NotesAdapter(val notes: List<Note>) : RecyclerView.Adapter<NotesAdapter.NoteViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val binding = NoteLayoutBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return NoteViewHolder(
binding,
LayoutInflater.from(parent.context)
.inflate(R.layout.note_layout, parent, false)
)
}
override fun getItemCount() = notes.size
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
holder.binding.textViewTitle.text = notes[position].title
holder.binding.textViewNote.text = notes[position].note
//Update existed Note
holder.view.setOnClickListener {
val action = HomeFragmentDirections.actionAddNote()
action.note = notes[position]
Navigation.findNavController(it).navigate(action)
}
}
class NoteViewHolder(var binding: NoteLayoutBinding, var view: View)
:RecyclerView.ViewHolder(binding.root)
}
But When I change argument from binding.root to view in NoteViewHolder,
I can edit item If I touched it. But item's preview shows default information which I set in note_layout.xml, not item's info.
Could anyone help me please?😂 If you don't have enough information in here, please comment.
With a few corrections, it will work fine.
class NotesAdapter(val notes: List<Note>) : RecyclerView.Adapter<NotesAdapter.NoteViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val binding = NoteLayoutBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
/*
return NoteViewHolder(
binding,
LayoutInflater.from(parent.context)
.inflate(R.layout.note_layout, parent, false)
)
this is not required, since you are using viewbinding, and you have already inflated binding
LayoutInflater.from(parent.context)
.inflate(R.layout.note_layout, parent, false)
*/
return NoteViewHolder(binding)
}
override fun getItemCount() = notes.size
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
holder.binding.textViewTitle.text = notes[position].title
holder.binding.textViewNote.text = notes[position].note
//holder.view.setOnClickListener, you inflated binding and you are listening
//to some other view
//Update existed Note
holder.binding.root.setOnClickListener {
val action = HomeFragmentDirections.actionAddNote()
action.note = notes[position]
Navigation.findNavController(it).navigate(action)
}
}
class NoteViewHolder(var binding: NoteLayoutBinding)
:RecyclerView.ViewHolder(binding.root)
}
I have a recyclerView populated by the val microphones: MutableList<Microphone>. There's a button in the row layout to remove that item from the list, which I've managed to get functioning, but the animation is going wrong. It looks like when the recyclerView updates, it removes the last item in the list, then the correct item, and then the whole list below the removed item animates to the correct state.
Here's the relevant bits of the recyclerView adapter:
class RecyclerViewAdapter(
val microphones: MutableList<Microphone>
): RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cellForRow = LayoutInflater.from(parent.context).inflate(R.layout.favorites_row, parent, false)
return ViewHolder(cellForRow)
}
override fun getItemCount(): Int {
return microphones.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val microphone = microphones[position]
holder.bind(microphone)
holder.favoritesButton.setOnClickListener {
microphones.remove(microphone)
notifyItemRemoved(position)
}
}
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val modelText = view.findViewById<TextView>(R.id.model_textView)
val brandText = view.findViewById<TextView>(R.id.brand_textView)
val favoritesButton = view.findViewById<ImageButton>(R.id.btnFavorite)
fun bind(
microphone: Microphone,
) {
modelText.text = microphone.model
brandText.text = microphone.brand
}
}
}
Here's a screen recording of the issue:
https://imgur.com/a/pJt3KwH
I have a vertical RecyclerView with LinearLayoutManager with different item types and one ViewHolder. It is a messaging screen.
I want show/hide message date on click on the cell.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layout = R.layout.xxx
val viewHolder = ViewHolder(LayoutInflater.from(parent.context).inflate(layout, parent, false))
viewHolder.itemView.layout?.setOnClickListener {
setMessageDateVisibility(viewHolder)
}
return viewHolder
}
So i added the method to reload view with notifyItemChanged(pos)
private fun setMessageDateVisibility(holder: ViewHolder) {
val pos = holder.adapterPosition
if (pos.toLong() == THREAD_DEFAULT_POSITION)
return
val message = mDataset[pos]
message.isDateVisible = !message.isDateVisible
notifyItemChanged(pos)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val message = mDataset[position]
itemView.message_date.text = message.date
itemView.message_date.visibility = if (message.isDateVisible)
View.VISIBLE
else
View.GONE
}
But the problem a the end, is that because of this line notifyItemChanged(pos), the override onBindViewHolder method called for all not visible cells and so my screen is lagging with 150/200 cells
Can you help me to find a solution ?