I am using a RecyclerView in a Fragment to display a list of elements, each containing a checkbox, as shown below.
Upon clicking the START button, I want to disable all of the elements in the RecyclerView.
Here is my code to do so:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
startButton = view.findViewById<Button>(R.id.start_button)
startButton.setOnClickListener {
// yes, recyclerView is defined
setViewGroupEnabled(recyclerView, false)
}
}
fun setViewGroupEnabled(view: ViewGroup, enabled: Boolean) {
Log.d(TAG, "Numkids: ${view.childCount}")
view.isEnabled = enabled
for (v in view.children) {
if (v is ViewGroup) setViewGroupEnabled(v, enabled)
else v.isEnabled = enabled
}
}
This code disables most of the elements in recyclerView, but for some reason it skips some, often multiple at a time. It also appears to skip subviews in a pattern that varies based on how far down the list I have scrolled.
Why is it behaving so strangely?
A RecyclerView has an Adapter. Its job is to handle the layout of each item of the RecyclerView. This includes disabling an item.
Add a class parameter to your adapter:
private var disabled = false
Add a method to your Adapter:
fun setDisabled(disabled: Boolean) {
this.disabled = disabled
notifyDatasetChanged()
}
In your onBindViewHolder method, check for the disabled parameter and disable the view as you want to:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (this.disabled) {
//disable you view, by disabling whichever subview you want
} else {
// The normal non disabled flow (what you have now)
}
}
Now call setDisabled(true) on a button click:
startButton.setOnClickListener {
// yes, recyclerView is defined
adapter.setDisabled(true)
}
And call setDisabled(false) to enable the items back.
Related
class Split_recycler_adapter (var arrayList: ArrayList<Split_recycler_model>) :
RecyclerView.Adapter<Split_recycler_adapter.Viewholder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Viewholder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.changesplit_recycler_item,parent,false)
return Viewholder(v)
}
override fun onBindViewHolder(holder: Viewholder, position: Int) {
val split_modal = arrayList[position]
var checked = true
Log.e("position_outside_loop->","$position")
holder.split_text.text = split_modal.getText()
holder.split_text.setOnClickListener{
if(checked){
Log.e("check->","Working")
holder.split_text.setBackgroundResource(R.drawable.green_button_gradient)
holder.split_text.setTextColor(Color.parseColor("#454546"))
checked = false
}else{
holder.split_text.setBackgroundResource(R.drawable.dropdown_gradient)
holder.split_text.setTextColor(Color.parseColor("#A4A4A4"))
checked = true
}
}
}
override fun getItemCount(): Int {
return arrayList.size
}
class Viewholder(Itemview: View) : RecyclerView.ViewHolder(Itemview) {
val split_text : TextView = Itemview.findViewById(R.id.split_text)
}
}
This code helps me to change the background of the button when clicked and revert the background when one more click is done but this is not the exact thing i want , I need to toggle the background on click, when one click is done it should want to remove the background of the other button , only one button should be highlighted at a time , Please help me if any one know the solution
You can't do it this way.
You have added a click listener in a binder and performed checked logic for that item mainly situated at that holder position. It won't affect other items at other positions. So to update other positions' state, you have to manage the state for each item by adding isChecked variable in Split_recycler_model class.
class Split_recycler_model {
var isChecked : Boolean = false
}
And in onClick of view, update your list item for that particular adapterPosition.
Updated click action,
holder.split_text.setOnClickListener{
checkItem(adapterPosition)
}
fun checkItem(adapterPosition:Int) {
arrayList.forEachIndexed{index,item ->
item.apply{
isChecked = (index == adapterPosition)
}
}
/**
Here you can manually calculate the changes
to update adapter to improve performance.
For now, I'm updating it with the `notifyDataSetChanged` method.
*/
notifyDataSetChanged()
}
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.
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 am trying to write a RecyclerView adapter class in Kotlin for android. I am trying to use the traditional way of creating a custom viewer class, for custom objects, and use a click listener in that. While I am able to do rest of the things like access the variables of inner class and show the RecyclerView, what I have not been able to do is add click listener to the var objects of inner class.
ie something like
var convertView : View? = itemView
convertView.setOnClickListener(this)
Following is my complete code of adapter class
public open class TestAdapter(val items: MutableList<Any>, val context: Activity) : RecyclerView.Adapter<TestAdapter.CustomViewHolder>() {
public var mItem: MutableList<Any> = items
public var mActivity: Activity = context
protected var clickListener: ExampleInterface? = null
public interface ExampleInterface {
fun click(pos: Int) {
}
}
open public fun setListener(mInterFaceListener: ExampleInterface) {
clickListener = mInterFaceListener
}
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): CustomViewHolder {
var parentLayout: View = LayoutInflater.from(mActivity).inflate(R.layout.custom_view, p0, false)
return CustomViewHolder(parentLayout)
// return CustomViewHolder(LayoutInflater.from(mActivity).inflate(R.layout.custom_view, p0, false))
}
override fun getItemCount(): Int {
return mItem.size
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onBindViewHolder(p0: CustomViewHolder, p1: Int) {
p0.dataView.text = mItem.get(p1).toString()
}
inner class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
var convertView: View? = itemView
var dataView: TextView = convertView!!.findViewById(R.id.data)
var mposition = adapterPosition
override fun onClick(p0: View?) {
if (clickListener != null) {
clickListener!!.click(mposition)
}
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
}
So if you see these two lines in CustomView class
var convertView: View? = itemView
var dataView: TextView = convertView!!.findViewById(R.id.data)
I cannot access these variables "convertView" and "dataView" so that I can set clicklistener to them. So how to achieve it ?
Thanks :)
I referred to this site
https://www.raywenderlich.com/367-android-recyclerview-tutorial-with-kotlin
Here I go to know my mistake , I need to use init in the class, there I am able to access it and initialize on click listener. Got it working
init {
convertView?.setOnClickListener(this)
}
Though the above answer could be acceptable as well, as I am new to Kotlin I cannot say which one is the better option, but my requirement is satisfied with the above mentioned site.
Thank you :)
In RecyclerView adapters, You could place the OnClickListeners in onCreateViewHolder in order to prevent them from being set each time onBindViewHolder is called (as onBindViewHolder is called multiple times when RecyclerView is scrolled). Use parentLayout in your onCreateViewHolder to access your views and set onClickListener to them. to determine current position in onCreateViewHolder you can do as below :
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): CustomViewHolder {
val parentLayout: View = LayoutInflater.from(mActivity).inflate(R.layout.custom_view, p0, false)
val customViewHolder = CustomViewHolder(parentLayout)
parentLayout.myExampleTextView.setOnClickListener {
// Place onClick logic here.
// If you need position, do as bellow :
val adapterPosition = customViewHolder.adapterPosition
if(adapterPosition != RecyclerView.NO_POSITION) {
// Place your position dependent logic here
}
}
return customViewHolder
}
UPDATE:
I updated the code snippet above and added the RecyclerView.NO_POSITION (which is equal to -1) check. The reason for the position being returned as -1 is explained below.
From android docs:
The other set of position related methods are in the form of
AdapterPosition. (e.g. getAdapterPosition(), findViewHolderForAdapterPosition(int)) You should use these methods
when you need to work with up-to-date adapter positions even if they
may not have been reflected to layout yet. For example, if you want to
access the item in the adapter on a ViewHolder click, you should use
getAdapterPosition(). Beware that these methods may not be able to
calculate adapter positions if notifyDataSetChanged() has been called
and new layout has not yet been calculated. For this reasons, you
should carefully handle NO_POSITION or null results from these
methods.
you can access them in the onBindViewHolder using the p0 this way p0.dataView so there you can set listeners successfully
I have an app in Kotlin done by another developer. It sets Onclick listeners in "onBindViewHolder" method of RecycleView Adapter. Somehow after the view scrolls out of the visible area (i.e. user scrolls down) and the user scrolls back to top, the onclick method only gets called after the view is tapped TWICE. Don't understand what's going on, and my Kotlin knowledge is very little. This is how the onclick listener is set:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
//...
holder.itemView.editButton.onClick {
Log.d("Holder", "Click")
}
//...
}
It is working EXCEPT when the view gets out of screen. Also, onBindViewholder is not called for views just coming back from outside of the screen, which is not standard I believe?
There is no special settings for the recycleview either, just a simple linearlayout and a single view type.
Any ideas?
Try this code.
holder.itemView.editButton.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
Log.d("Holder", "Click")
}
})
Make sure you bind the value of edittext with ViewHolder class like below
class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
// Holds the button that will add each editButton to
val editButton = view.editButton
}
After try this simple code
holder?.editButton.setOnClickListener {
Log.e("editbutton", "onClick")
}