I have a RecyclerView which has an EditText in its items.
For each EditText I have added a textChangeListener.
Whenever the user types a character I want to do an input validation and so I can change the item, for example show red border in case of wrong input etc...
After I do the validation I call:
adapter.notifyItemChanged(itemPosition)
to update the view.
But the problem is that the focus is lost from the EditText and I have to click again inside the box in order to continue typing.
Is there a solution for this case? How can I continue typing while the view is updated after calling notifyItemChanged ?
ADAPTER
class ItemAdapter() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = LayoutItemBinding.inflate(inflater, parent, false)
binding.editText.addTextChangedListener {
// Do layout changes....
if (isInvalidInput) {
item.setError = true
....
...
}
adapter.notifyItemChanged(itemPosition)
}
return ItemViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(items[position])
}
inner class ItemViewHolder(val binding: LayoutItemBinding)
: RecyclerView.ViewHolder(binding.root) {
fun bind(model: ItemModel) {
// Setup view for item
}
}
}
Try to move your error setting code in the bind function of the ViewHolder and update the error status outside of the TextWatcher:
class ItemAdapter() : RecyclerView.Adapter<RecyclerView.ViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = LayoutItemBinding.inflate(inflater, parent, false)
return ItemViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(items[position], position)
}
inner class ItemViewHolder(val binding: LayoutItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(model: ItemModel, position: Int) {
binding.editText.addTextChangedListener {
// Update the ItemModel on text change
adapter.notifyItemChanged(position)
}
// Update the view error status everytime it is updated
val isInvalidInput = ... // Add your logic
if (isInvalidInput) {
model.setError = true
....
....
}
}
}
}
First, Please post your code.
If you called notifyDatasetChanged(), all views of recyclerview will refreshed (it means that the views could be recreated.).
So if you want remain focus in edittext, you not call notifyDatasetChanged() or set focus to your edittext after notifyDataChanged().
But setFocus is bit more difficult.
So I suggest method that update views of recyclerview without call notifyDatasetChanged().
I think you can directly update view of recyclerview without call notifyDataChanged().
You should save the id of item that contains the EditText that is editing. And in the bind() method of ViewHolder, you will check if the item is currently being edit, you will call requestFocus() method on EditText.
And please take note that when you call notifyDataChanged(), the RecycleView maybe check the data of each item to know whether it was changed or not. If the data of an item was changed, RecycleView will redraw that item.
Related
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 have differents screens that use recyclerview but they have a very similar item layout. The item layout is basically a textview and an image. The image is the same and never changes and the textview style is the same but the text changes because it comes from the api.
I started using the same item layout for each recyclerview because it was very similar but i don't know how to make it work and if it would take too much time
Below is the simplest way to change what to display. You can pass a boolean in the constructor of the adapter from the parent classes.
For example, If it is true, show some data. If it is false you can show some other data.
class RecyclerAdapter(var displayUserData: Boolean) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {...}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = values[position]
if(displayUserData){
holder.textView.text = item.name
}else{
holder.textView .text = item.otherStuff
}
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {...}
}
Then set the adapter with true or false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val adapter = RecyclerAdapter(true)
}
I am researching on how to add an onClick event on my recyclerview properly,
currently I am using the interface inside my customAdapter
class CategoryAdapter(val categoryList : List<CategoryObject>, val context: Context, val mItemClickListener: MainInterface) : RecyclerView.Adapter<CategoryAdapter.ViewHolder>() {
interface MainInterface {
fun onCategoryItemClick(categoryKey: Int)
}
override fun getItemCount(): Int {
return categoryList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.listview_category, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder?.categoryName?.text = categoryList[position].categoryName
// holder?.categoryName.setOnClickListener{
// mItemClickListener.onCategoryItemClick(position)
// }
// holder?.categoryName?.setOnClickListener { listener(categoryList[position]) }
}
inner class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val categoryName = view.lv_txt_category_name
init {
view.setOnClickListener {
mItemClickListener.onCategoryItemClick(adapterPosition)
}
}
}
}
viewHolder's onClick does not register on my activity ovveride function.
but putting the onClick inside onBindViewHolder works perfectly,
I'm not sure which is more efficient, if the onBindViewHolder onClick is the right answer, then I'll stick with it, but if the viewHolder is the right one, why it does not work properly?
Thanks in advance!
Update
This is the stackoverflow post I'm using to research things
RecyclerView itemClickListener in Kotlin
tried android:clickable="true" in your xml?
As the documentation suggest for:
RecyclerView.Adapter.html#onBindViewHolder
Called by RecyclerView to display the data at the specified position.
This method should update the contents of the itemView to reflect the
item at the given position.
And for the RecyclerView.Adapter.html#onCreateViewHolder
Called when RecyclerView needs a new RecyclerView.ViewHolder of the
given type to represent an item.
This new ViewHolder should be constructed with a new View that can
represent the items of the given type. You can either create a new
View manually or inflate it from an XML layout file.
As onCreateViewHolder is a method where we return the type of ViewHolder only while in onBindViewHolder we update or initialise the data to display.
It means no data is binded in onCreateViewHolder and binding anything in this method will have no effect.
There is a Recyclerview in my activity. it has always 10 items. Recyclerview's item has two buttons witch each one's click listener is defined in onBindViewHolder method of adapter class. after clicking any of the items's buttons it calls a callback method witch is implemented in activity's onCreate method. The callback method passes adapterPosition to activity an activity returns a long value witch I update button's text with that.
The problem is when I click the first item's button of the recyclerview, it updates the first and the last item's text. First I used position but then changed it to adapterPosition or layoutPosition, but it didn't worked. sometime the one before last item gets updated and sometimes the last one gets updated with the updating of the first item
activity's onCreate code
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_videocrop)
var segmentAdapter = SegmentAdapter(10)
var layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
videoCropRecyclerView.adapter = segmentAdapter
videoCropRecyclerView.layoutManager = layoutManager
segmentAdapter.onSegmentSelectedListener = object : OnSegmentSelectedListener {
override fun onSelectFrom(index: Int): Int? {
return 12345
}
override fun onSelectTo(index: Int): Int? {
return 54321
}
}
}
Adapter class code
class SegmentAdapter(var segments: Int) : RecyclerView.Adapter<SegmentAdapter.SegmentViewHolder>() {
var onSegmentSelectedListener: OnSegmentSelectedListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SegmentViewHolder =
SegmentViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.videocrop_segment_item, parent, false))
override fun getItemCount(): Int = segments
override fun onBindViewHolder(holder: SegmentViewHolder, position: Int) {
holder.from!!.setOnClickListener {
holder.from!!.text = onSegmentSelectedListener!!.onSelectFrom(holder.adapterPosition)
}
holder.to!!.setOnClickListener {
holder.to!!.text = onSegmentSelectedListener!!.onSelectTo(holder.adapterPosition)
}
}
class SegmentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var from: Button? = null
var to: Button? = null
init {
from = itemView.findViewById(R.id.videoCropSegmentItemFromButton)
to = itemView.findViewById(R.id.videoCropSegmentItemToButton)
}
}
}
Callback Interface Code
interface OnSegmentSelectedListener {
fun onSelectFrom(index:Int):Int?
fun onSelectTo(index:Int):Int?
}
when I click the first item's button and its text becomes "0"
then I scroll down to the last item and I see the last item is also updated
I also checked this question but it wasn't helpful.
I would be very thankful if somebody helps me
Your onBindViewHolder method is not handling recycled views. You need to set the text every time in onBindViewHolder based on the backing data, rather than just setting it in the click listener, because you may get an itemView re-used from a different item (in this case, the first item where you've changed the text).
I have a RecyclerView.Adapter like this:
internal class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
private val data: List<MyModel> = SeedData().seed()
override fun onCreateViewHolder(v: ViewGroup, viewType: Int): MyViewHolder {
val binding = MyListitemBinding.inflate(LayoutInflater.from(v.context), v, false)
return MyViewHolder(binding)
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(data[position])
}
}
However, only the first item from the data is getting displayed (i.e. onCreateViewHolder & onBindViewHolder invoked only one time). How can I make it display all items from the data properly?
The commented answers above are correct. My list item (views) were full height of screen:
This means that RecyclerView would only update the ViewHolder once you scroll to the next element. The solution is to modify the height of these items.