Kotlin - Recycleview ViewHolder OnClick not working - android

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")
}

Related

How can I change or replace layout on the right by clicking on a recycleview item on the left same page- android kotlin

I have a layout that is divided into two, the RecyleView on the left and the details on the right. I would like that when I click an item on the left the layout on the right is replace based on the item clicked. Can someone help me achieve this, or help with a better approach to this. Thank you
You can try this simple solution, use interface.
In your adapter create a interface which is used for implement onClick in your item. Then, call it in onBindViewHolder. For example:
class YourAdapter(val onclick: OnClickListener): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
interface OnClickListener {
fun onClickItem(position: Int)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
onclick.onClickItem(position) // Here i will pass position for Ex, you can pass anything you want depend on
// what's your declared in parameter of onClickItem function
}
}
Declare adapter and implement interface in your Activity.
class YourActivity() : YourAdapter.OnClickListener() {
lateinit var adapter: YourAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
adapter = YourAdapter(this)
}
override fun onClickItem(position: Int) {
// now you can set content to the view on the right, i've pass position item of recyclerView over here.
// content will change based on item you click on the left.
// Ex, i have a TextView on the right.
myTextview.settext(position.toString())
}
}
Your answer helped me. But what I did in the onBindViewHolder, I used [position], if the position is say one, then am able to replace the fragment with the new one:
holder.itemView.setOnClickListener {
if (position == 1) {
val activity = it.context as AppCompatActivity
val menFragment = MenFragment()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.frameLayout, menFragment)
.commit()
}
}

Disable all subviews of RecyclerView

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.

Interface between recyclerview adapter & viewholder

So I have a Recyclerview & multiple viewholders. Now one of my viewholder contains an item that has few animations. Now if a user clicks on the item activity opens and I have no control over the activity, the only way I got to know is via the callback in my fragment i.e onPause & onResume. I want to call animation.onResume() once my fragment onResume() is invoked.
Interface class
interface CustomTabClosedListener {
fun onCustomTabClosed()
}
Adapter Relevant code:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
lateinit var viewViewHolder: RecyclerView.ViewHolder
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater!!.inflate(R.layout.feed_coverage, parent, false)
viewViewHolder = CoverageViewHolder(view)
listener = viewViewHolder
}
where listener is private var listener: CustomTabClosedListener?=null
ViewHolder
class CoverageViewHolder(val view: View) :CustomTabClosedListener {
override fun onCustomTabClosed() {
animation?.resume()
Now my fragment calls this adapter method in onResume -
fun customTabClosed() {
listener?.onCustomTabClosed()
}
But here my listener is getting null and I don't know the reason why. How can i invoke my method in viewholder via my adapter/fragment? Is there any other better approach?
I guess the reason why you get null is because you should have only a single call back in the Adapter viewHolder, and all the animation should be managed by the view. Is better and clean if the adapter manage some really elementar business logic, leaving what should happen to the view into the view itself.
Let's say you have an xml file with your animation
you could have from the view the control to notify the adapter and start the animation
fun IamACoolCallBack(){
AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_fall_down);
with(recyclerView){
setLayoutAnimation(controller);
getAdapter().notifyDataSetChanged();
scheduleLayoutAnimation();
}
}
EDIT
I found this tutorial it looks pretty neat to me

Android Recyclerview's last item gets updated by updating the first 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).

Cannot access var object of inner class in kotlin

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

Categories

Resources