I'm writing my first app in Kotlin after 3 years of experience with Android.
Just confused as to how to utilize itemClickListener with a RecyclerView in Kotlin.
I have tried the trait (edit: now interface) approach, very Java-like
public class MainActivity : ActionBarActivity() {
protected override fun onCreate(savedInstanceState: Bundle?) {
// set content view etc go above this line
class itemClickListener : ItemClickListener {
override fun onItemClick(view: View, position: Int) {
Toast.makeText(this#MainActivity, "TEST: " + position, Toast.LENGTH_SHORT).show()
}
}
val adapter = DrawerAdapter(itemClickListener())
mRecyclerView.setAdapter(adapter)
}
trait ItemClickListener {
fun onItemClick(view: View, position: Int)
}
}
That seemed very redundant so I tried the inner class approach:
inner class ItemClickListener {
fun onItemClick(view: View, position: Int) {
startActivityFromFragmentForResult<SelectExerciseActivity>(SELECT_EXERCISES)
}
}
And then just setting the adapter's click listener like this:
val adapter = WorkoutsAdapter(ItemClickListener())
But I'm still not satisfied with this because I think there might be a better, cleaner way. I'm trying to essentially achieve something like this:
RecyclerView onClick
Any suggestions?
Ended up going with a variation of the approved answer
Defined the function in the activity:
val itemOnClick: (View, Int, Int) -> Unit = { view, position, type ->
Log.d(TAG, "test")
}
Passed the function itself on to the adapter like this:
class ExercisesAdapter(val itemClickListener: (View, Int, Int) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// other stuff up here
val vhExercise = ExerciseVH(view) // view holder
// on to the view holder through the extension function
vhExercise.onClick(itemClickListener)
}
}
Extension function by Loop in the approved answer below.
fun <T : RecyclerView.ViewHolder> T.onClick(event: (view: View, position: Int, type: Int) -> Unit): T {
itemView.setOnClickListener {
event.invoke(it, getAdapterPosition(), getItemViewType())
}
return this
}
My solution is like a combination of the previous ones with a super clean call from the activity.
ContactAdapter:
class ContactAdapter #Inject constructor() : RecyclerView.Adapter<ContactAdapter.ViewHolder>() {
var onItemClick: ((Contact) -> Unit)? = null
var contacts: List<Contact> = emptyList()
...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contact = contacts[position]
holder.email.text = contact.email
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val email: TextView = itemView.email
init {
itemView.setOnClickListener {
onItemClick?.invoke(contacts[adapterPosition])
}
}
}
}
ContactActivity:
override fun setupRecyclerAdapter() {
recyclerView.adapter = contactAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
contactAdapter.onItemClick = { contact ->
// do something with your item
Log.d("TAG", contact.email)
}
}
I have a little bit different approach. You can create an extension for your ViewHolder
fun <T : RecyclerView.ViewHolder> T.listen(event: (position: Int, type: Int) -> Unit): T {
itemView.setOnClickListener {
event.invoke(getAdapterPosition(), getItemViewType())
}
return this
}
Then use it in adapter like this
class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
val items: MutableList<String> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MyViewHolder? {
val inflater = LayoutInflater.from(parent!!.getContext())
val view = inflater.inflate(R.layout.item_view, parent, false)
return MyViewHolder(view).listen { pos, type ->
val item = items.get(pos)
//TODO do other stuff here
}
}
override fun onBindViewHolder(holder: MyViewHolder?, position: Int) {
}
override fun getItemCount(): Int {
return items.size()
}
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
}
}
I am working with my colleagues on library providing such extensions.
In case anyone is looking for a more no-frills answer, I tried the following - which is very similar to the solution from AfzalivE:
In my Adapter class I passed the clickListener as a parameter. On onBindViewHolder, I've used setOnClickListener to call clickListener and handle click event.
MyAdapter.kt:
class MyAdapter constructor(objects: ArrayList<MyObject>, val clickListener: (MyObject) -> Unit) : RecyclerView.Adapter<MyAdapter.Holder>() {
private var mObjects : ArrayList<MyObject> = ArrayList<MyObject>()
init {
mObjects = objects
}
override fun onBindViewHolder(holder: Holder?, position: Int) {
var item : MyObject = objects[position]
// Calling the clickListener sent by the constructor
holder?.containerView?.setOnClickListener { clickListener(item) }
}
// More code (ViewHolder definitions and stuff)...
}
Note: I needed a reference from my list item's container (the root view), which in this case is containerView
Then I passed my object as parameter without need for searching it on a list again and handle it directly on my Activity class, in the moment I set the adapter:
MyActivity.kt:
myRecyclerView?.adapter = MyAdapter(mObjects) {
Log.e("Activity", "Clicked on item ${it.itemName}")
}
Update
If you need to get the position of the clicked item, just define it as parameter on the callback and then send it back later. Notice the val clickListener: (MyObject, Int) -> Unit below:
MyAdapter.kt
class MyAdapter constructor(objects: ArrayList<MyObject>, val clickListener: (MyObject, Int) -> Unit) : RecyclerView.Adapter<MyAdapter.Holder>() {
// Rest of the code...
Then on onBindViewHolder() you pass the position when calling the callback method:
override fun onBindViewHolder(holder: Holder?, position: Int) {
var item : MyObject = objects[position]
// Calling the clickListener sent by the constructor
holder?.containerView?.setOnClickListener { clickListener(item, position) }
}
And on MyActivity.kt, you'll have to change the way you set the adapter so you can get the position. Like this:
myRecyclerView?.adapter = MyAdapter(mObjects) { itemDto: MyObject, position: Int ->
Log.e("MyActivity", "Clicked on item ${itemDto.someItemPropertyLikeName} at position $position")
}
Sorry for the delay, Got an awesome answer from this link and it was in Java..
Did some Homework and converted it to Kotlin..
Now it is working Properly.. Here is the Code,
Create a class named RecyclerItemClickListenr,
class RecyclerItemClickListenr(context: Context, recyclerView: RecyclerView, private val mListener: OnItemClickListener?) : RecyclerView.OnItemTouchListener {
private val mGestureDetector: GestureDetector
interface OnItemClickListener {
fun onItemClick(view: View, position: Int)
fun onItemLongClick(view: View?, position: Int)
}
init {
mGestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent): Boolean {
return true
}
override fun onLongPress(e: MotionEvent) {
val childView = recyclerView.findChildViewUnder(e.x, e.y)
if (childView != null && mListener != null) {
mListener.onItemLongClick(childView, recyclerView.getChildAdapterPosition(childView))
}
}
})
}
override fun onInterceptTouchEvent(view: RecyclerView, e: MotionEvent): Boolean {
val childView = view.findChildViewUnder(e.x, e.y)
if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
mListener.onItemClick(childView, view.getChildAdapterPosition(childView))
}
return false
}
override fun onTouchEvent(view: RecyclerView, motionEvent: MotionEvent) {}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}}
and access it from Activity/Fragment as
recyclerView.addOnItemTouchListener(RecyclerItemClickListenr(this, recyclerView, object : RecyclerItemClickListenr.OnItemClickListener {
override fun onItemClick(view: View, position: Int) {
//do your work here..
}
override fun onItemLongClick(view: View?, position: Int) {
TODO("do nothing")
}
}))
Add ClickListener code on onBindViewHolder fun
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.vieww.textView.setText(arr.get(position))
holder.vieww.setOnClickListener {(holder.vieww.textView.setTextColor(Color.GREEN))} // click event
}
You can easily achieve this by using an interface
class ExercisesAdapter(private val itemClickListener: ItemClickListener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
interface ItemClickListener {
fun onItemClick(position: Int)
fun onLongClick(position: Int)
}
inner class MyViewHolder(view:View): RecyclerView.ViewHolder(view){
init {
view.setOnClickListener {
if (bindingAdapterPosition >= 0) {
itemClickListener.onItemClick(bindingAdapterPosition)
}
}
view.setOnLongClickListener{
if (bindingAdapterPosition >= 0) {
itemClickListener.onLongClick(bindingAdapterPosition)
}
return#setOnLongClickListener true
}
}
}
}
From your MainActivity
public class MainActivity : AppCompatActivity(), ExercisesAdapter.ItemClickListener {
protected override fun onCreate(savedInstanceState: Bundle?) {
// set content view etc go above this line
mAdapter = ExercisesAdapter(this)
}
override fun onItemClick(position: Int) {
Toast.makeText(this#MainActivity, "TEST: " + position, Toast.LENGTH_SHORT).show()
}
override fun onLongClick(position: Int) {
//do long click here
}
}
Slightly different, based on denwehrle
To use on a fragment, inside OnCreateView
adapter.onItemClick = {it ->
//do something
}
Add in the adapter class:
var onItemClick: ((Contact)->Unit) ?= null
...
inner class contactViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val myItemView: TextView = itemView.findViewById(R.id.textView)
init{
itemView.setOnClickListener {
onItemClick?.invoke(contact[adapterPosition])
}
}
}
You don't need to write extension function to ViewHolder or something like this.
Best practice; use Higher-Order Function
MainRecyclerAdapter
class MainRecyclerAdapter(val news: JSONArray, private val itemClickListener: (Int) -> Unit) : RecyclerView.Adapter<MainRecyclerAdapter.ViewHolder>() {}
Just add a Higher-order func. like itemClickListener and then go to the ViewHolder class. Write this function to your bind function as parameter and set this to itemView Like that :
MainRecyclerAdapter.ViewHolder
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(newsItem: JSONObject,itemClickListener:(Int)->Unit) {
//Some Stuff here..
itemView.setOnClickListener { itemClickListener(adapterPosition) }
}
}
Use this method onBindViewHolder
OnBindViewHolder
override fun onBindViewHolder(holder: MainRecyclerAdapter.ViewHolder, position: Int) {
holder.bind(news.getJSONObject(position),itemClickListener)
}
And now you can write your onClick function in any activity or fragments.. Just give as parameter.
Activity or Fragment
val itemOnClick: (Int) -> Unit = { position ->
newsRecyclerView.adapter!!.notifyDataSetChanged()
Toast.makeText(this.context,"$position. item clicked.",Toast.LENGTH_SHORT).show()
}
newsRecyclerView.adapter = MainRecyclerAdapter(news,itemClickListener = itemOnClick)
Updated in 05 - 2019
I think the most elegant solution is to give this responsibility to recyclerView and not to view or even adapt it.
for that we need:
1: Create RecyclerItemClickListener file
class RecyclerItemClickListener(
private val mRecycler: RecyclerView,
private val clickListener: ((position: Int, view: View) -> Unit)? = null,
private val longClickListener: ((position: Int, view: View) -> Unit)? = null
) : RecyclerView.OnChildAttachStateChangeListener {
override fun onChildViewDetachedFromWindow(view: View) {
view.setOnClickListener(null)
view.setOnLongClickListener(null)
}
override fun onChildViewAttachedToWindow(view: View) {
view.setOnClickListener { v -> setOnChildAttachedToWindow(v) }
}
private fun setOnChildAttachedToWindow(v: View?) {
if (v != null) {
val position = mRecycler.getChildLayoutPosition(v)
if (position >= 0) {
clickListener?.invoke(position, v)
longClickListener?.invoke(position, v)
}
}
}
}
2: Create/Add extensions for RecyclerView:
import android.support.v7.widget.RecyclerView
import com.app.manager.internal.common.RecyclerItemClickListener
#JvmOverloads
fun RecyclerView.affectOnItemClicks(onClick: ((position: Int, view: View) -> Unit)? = null, onLongClick: ((position: Int, view: View) -> Unit)? = null) {
this.addOnChildAttachStateChangeListener(RecyclerItemClickListener(this, onClick, onLongClick))
}
3: And finally the use (i suppose you use kotlinx)
import kotlinx.android.synthetic.main.{your_layout_name}.*
class FragmentName : Fragment() {
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
recycler.affectOnItemClick { position, view -> /*todo*/ }
}
}
3 easy steps:
1. pass in argument of your adapter as follow:
class ListAdapter(private val mListener: (ListItemDataClass) -> Unit)
2. in onBindViewHolder function, use like this
override fun onBindViewHolder(holder: YourViewHolder, position: Int) {
val item = getItem(position)
holder.itemView.setOnClickListener {
item?.let { it1 -> mListener.invoke(it1) }
}
}
3. and in your activity, use like this
val adapter = ListAdapter {
Toast.makeText(this, it.title, Toast.LENGTH_SHORT).show()
}
Here is a simple approach without using an interface, just in your adapter create an init block within viewholder class. Like below
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
itemView.setOnClickListener{
//your code here---
}
}
}
Adapter constructor declaration
class YourAdapter(private val mListener: (ItemObject) -> Unit) : RecyclerView.Adapter<ViewHolder>()
Adapter::onBindViewHolder
holder.itemView.setOnClickListener {
mListener.invoke(item) // <- item instance of ItemObject
}
How to Use
mTypesWasteAdapter = YourAdapter({ it.something()})
Basically, you will receive the ItemObject as it in the lambda argument.
Finally, here is a nice working solution:
MyRecyclerAdapter.kt
class MyRecyclerAdapter(val context: Context, val items : ArrayList<Item>, val clickListener: (Int) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, p1: Int): RecyclerView.ViewHolder {
return MyViewHolder(LayoutInflater.from(context).inflate(R.layout.my_item, parent, false))
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as MyViewHolder).clickableView.setOnClickListener {
clickListener(position)
}
}
}
class MyViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val clickableView = view.clickable_view
}
MainActivity.kt
fun appClickListener(position: Int) {
// You got the position of ArrayList
}
my_recyclerview.adapter = MyRecyclerAdapter(this, myList, clickListener = {
appClickListener(it)
})
My simple solution using higher-order function and let scoping function to set listener only if itemAction has been set
// Adapter
private var itemAction: ((Item) -> Unit)? = null
fun setItemAction(action: (Item) -> Unit) {
this.itemAction = action
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun setItem(item: Item) {
// ...
itemAction?.let {
itemView.setOnClickListener { it(item) }
}
}
}
and in activity/fragment
adapter.setItemAction { // <- it is Item
// do something with it
}
You could try something like:
public class MainActivity : ActionBarActivity() {
protected override fun onCreate(savedInstanceState: Bundle?) {
[...]
val adapter = DrawAdapter(::onItemClick)
[...]
}
}
fun onItemClick(view: View, position: Int) {
//Do work
}
and SAM convertion just works like in Java 8, so just use a lambda:
public class MainActivity : ActionBarActivity() {
protected override fun onCreate(savedInstanceState: Bundle?) {
[...]
val adapter = DrawAdapter({view, position -> /*Do work*/ })
[...]
}
}
In RecyclerView you can put click on inflated view inside ViewHolder class and call it from onBindViewHolder callback method for example:
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val view = view
val tv_message = view.tv_message
val tv_address = view.tv_address
fun bind(listViewItem: ListViewItem) {
view.setOnClickListener(View.OnClickListener {
Toast.makeText(
view.context,
"Name: " + listViewItem.name + "/n Address: " + listViewItem.address,
Toast.LENGTH_LONG).show()
})
}
}
}
You can call from adapter onBindViewHolder() method:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val listViewItem: ListViewItem = mListViewItems[position]
holder.tv_message.text = listViewItem.name
holder.tv_address.text = listViewItem.address
holder.bind(mListViewItems[position]);
}
//Step 1 make an interface like
interface RecyclerViewClickListener {
fun onItemClick(position: String)
fun onLongClick(position: Int)
}
Step 2 Inside Adapter class pass one more argument as an interface like
class ModelAdapter(var item_list: ArrayList<UploadDocument>,var mItemClickListener:RecyclerViewClickListener) : RecyclerView.Adapter<ModelAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ModelAdapter.ViewHolder {
// create a new view
val view = LayoutInflater.from(parent.context).inflate(R.layout.upload_document_row_item, null)
// create ViewHolder
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ModelAdapter.ViewHolder, position: Int) {
holder.txtRegistrationDoc?.setText(item_list[position].getdocName())
holder.txtCertificate?.setText(item_list[position].getcertificateName())
holder.txtFileSize?.setText(item_list[position].getfileSize())
holder.txtCreatedOn?.setText(item_list[position].getcreatedOn())
holder.txtModifiedOn?.setText(item_list[position].getModifiedDate())
//holder.chkSelected.isChecked = item_list[position].isSelected()
holder.chkSelected.tag = item_list[position].getdocName()
holder. chkSelected!!.setOnCheckedChangeListener { buttonView, isChecked ->
if(isChecked)
{
System.out.println("position>>>"+buttonView.tag.toString())
mItemClickListener.onItemClick(buttonView.tag.toString())
}
}
//(context as UploadDocumentActivity::class.java).onClickCalled("your argument here")
/* holder.btn_delete.setOnClickListener(object : View.OnClickListener() {
override fun onClick(v: View) {
deleteItemFromList(v, position)
}
})*/
}
override fun getItemCount(): Int {
return item_list.size
}
/*// confirmation dialog box to delete an unit
private fun deleteItemFromList(v: View, position: Int) {
val builder = AlertDialog.Builder(v.getContext())
//builder.setTitle("Dlete ");
builder.setMessage("Delete Item ?")
.setCancelable(false)
.setPositiveButton("CONFIRM",
DialogInterface.OnClickListener { dialog, id ->
item_list.remove(position)
notifyDataSetChanged()
})
.setNegativeButton("CANCEL", DialogInterface.OnClickListener { dialog, id -> })
builder.show()
}*/
class ViewHolder(
itemLayoutView: View) : RecyclerView.ViewHolder(itemLayoutView) {
var item_name: TextView
var txtRegistrationDoc: TextViewNormal?=null
var txtCertificate: TextViewNormal?=null
var txtFileSize: TextViewNormal?=null
var txtCreatedOn: TextViewNormal?=null
var txtModifiedOn: TextViewNormal?=null
var chkSelected: CheckBox
init {
item_name = itemLayoutView.findViewById(R.id.txt_Name)
txtRegistrationDoc = itemLayoutView.findViewById(R.id.txtRegistrationDoc)
txtCertificate = itemLayoutView.findViewById(R.id.txtCertificate)
txtFileSize = itemLayoutView.findViewById(R.id.txtFileSize)
txtCreatedOn = itemLayoutView.findViewById(R.id.txtCreatedOn)
txtModifiedOn = itemLayoutView.findViewById(R.id.txtModifiedOn)
//btn_delete = itemLayoutView.findViewById(R.id.btn_delete_unit)
chkSelected = itemLayoutView.findViewById(R.id.chk_selected)
}
}
}
//Step 3 Inside your activity/ Frgament
recyclerView?.adapter = ModelAdapter(documentList,object : `in`.mobilepedia.com.gicgwaliarincubationcentre.RecyclerViewClickListener
{
override fun onItemClick(position: String) {
System.out.println("Position>>>>>"+position)
}
override fun onLongClick(position: Int) {
}
})
Kotlin
Make your adapter constructor like this
class ViewAdapter(
private val context: Context,
private val mListener: (DataClass) -> Unit
) :
RecyclerView.Adapter<WeekRecyclerViewAdapter.ViewHolder>() {
// Your adapter code goes here
}
In your onBindViewHolder,
holder.binding.parentLayout.setOnClickListener {
mListener.invoke(items[position]) // <- item instance of ItemObject
}
In your Fragment, implement like below
class YourFragment : Fragment(), (DataClass) -> Unit {
override fun invoke(p1: DataClass) {
//You will get the selected item here
}
Here is my MainActivity.kt class that uses recyclerview to populate location data. It has a simple on item click listener interface that you can implement.
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var viewAdapter: RecyclerView.Adapter<*>
private lateinit var viewManager: RecyclerView.LayoutManager
private var locationArrayList = arrayListOf<Location>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//create locations
var ny = Location("New York")
var la = Location("Los Angeles")
locationArrayList.addAll(listOf(ny, la))
viewManager = LinearLayoutManager(this)
viewAdapter = LocationsAdapter(locationArrayList)
recyclerView = findViewById<RecyclerView>(R.id.recyclerView).apply {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
setHasFixedSize(true)
// use a linear layout manager
layoutManager = viewManager
// specify an viewAdapter
adapter = viewAdapter
}
//recycler view click listener implement
recyclerView.addOnItemClickListener(object: OnItemClickListener {
override fun onItemClicked(position: Int, view: View) {
// Your logic
Toast.makeText(this#MainActivity, locationArrayList[position].locationName, Toast.LENGTH_SHORT).show()
}
})
}
//on item click interface
interface OnItemClickListener {
fun onItemClicked(position: Int, view: View)
}
fun RecyclerView.addOnItemClickListener(onClickListener: OnItemClickListener) {
this.addOnChildAttachStateChangeListener(object: RecyclerView.OnChildAttachStateChangeListener {
override fun onChildViewDetachedFromWindow(view: View?) {
view?.setOnClickListener(null)
}
override fun onChildViewAttachedToWindow(view: View?) {
view?.setOnClickListener({
val holder = getChildViewHolder(view)
onClickListener.onItemClicked(holder.adapterPosition, view)
})
}
})
}
//end of interface
}
If someone interested in the old way implementation..
I posted full example which also reduces your adapter code as well. It uses the old pattern of getting callback..
Project level gradle
buildscript {
ext.kotlin_version = '1.3.10'
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"//newly added
classpath 'com.google.gms:google-services:4.1.0' // google-services plugin
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
App level Gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'org.jetbrains.kotlin.android.extensions'//it is used for #Percelize
android {
compileSdkVersion 28
dataBinding {
enabled = true
}
androidExtensions {
experimental = true
}
defaultConfig {
applicationId 'broadpeak.firebase.learning'
minSdkVersion 19
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors {
}
}
/*kapt {
generateStubs = true
}*/
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.google.firebase:firebase-core:16.0.5'
implementation 'com.google.firebase:firebase-firestore:17.1.3'
implementation 'com.google.firebase:firebase-auth:16.0.5'
implementation 'com.google.firebase:firebase-messaging:17.3.4'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.firebaseui:firebase-ui-auth:4.1.0'
implementation 'com.github.bumptech.glide:glide:4.8.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
////kapt "com.android.databinding:compiler:$android_plugin_version"\ // not required above 3.2.0
///kapt "com.android.databinding:compiler:3.1.4"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
// ADD THIS AT THE BOTTOM
apply plugin: 'com.google.gms.google-services'
SubjectListActivity.class
class SubjectListActivity : BaseActivity() {
var subjects = mutableListOf<SubjectBO>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.question_list_activity)
recycler_view.itemAnimator = DefaultItemAnimator()
recycler_view.setHasFixedSize(true)
recycler_view.layoutManager = LinearLayoutManager(this#SubjectListActivity)
db.collection("tagCollection").get().addOnSuccessListener { querySnapshot ->
if (querySnapshot.isEmpty()) {
Log.d(TAG, "onSuccess: LIST EMPTY")
} else {
// Convert the whole Query Snapshot to a list
// of objects directly! No need to fetch each document.
subjects = querySnapshot.toObjects(SubjectBO::class.java)
if(subjects.size > 0){
recycler_view.adapter = SubjectAdapter(subjects, object : OnRecyclerItemClickListener {
override fun onItemClicked(view: View?, position: Int) {
var intent = Intent(this#SubjectListActivity,McqActivity::class.java)
intent.putExtra("keyTagBO",subjects.get(position))
startActivity(intent)
}
});
}
}
}.addOnFailureListener { exception ->
exception.printStackTrace()
}
}
SubjectAdapter.class
class SubjectAdapter(items: List<SubjectBO>, onRecyclerItemClickListener: OnRecyclerItemClickListener)
: BaseAdapter<SubjectBO, SubjectViewHolder>(items, onRecyclerItemClickListener) {
override fun onCreateViewHolder(parent: ViewGroup, p1: Int): SubjectViewHolder {
return SubjectViewHolder(parent, R.layout.item_subject, onRecyclerItemClickListener)
}
}
SubjectViewHolder.class
class SubjectViewHolder(parent: ViewGroup, itemLayoutId: Int, onRecyclerItemClickListener:
OnRecyclerItemClickListener) : BaseViewHolder<SubjectBO>(parent, itemLayoutId, onRecyclerItemClickListener) {
override fun bindData(data: SubjectBO) {
itemView.tvTitle.setText(data.tagName)
}
}
BaseAdapter.class
abstract class BaseAdapter<T, U : BaseViewHolder<T>>
(var items: List<T>, var onRecyclerItemClickListener: OnRecyclerItemClickListener)
: RecyclerView.Adapter<U>() {
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: U, pos: Int) {
holder.bindData(items.get(pos))
}
}
BaseViewHolder.class
abstract class BaseViewHolder<T : BaseModel>(parent: ViewGroup, #LayoutRes itemLayoutId: Int,
var onRecyclerItemClickListener: OnRecyclerItemClickListener) :
RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(itemLayoutId, parent,
false)), View.OnClickListener {
override fun onClick(v: View?) {
onRecyclerItemClickListener.onItemClicked(v, adapterPosition)
}
abstract fun bindData(data: T)
init {
itemView.setOnClickListener(this)
}
}
OnRecyclerItemClickListener.class
interface OnRecyclerItemClickListener{
fun onItemClicked(view: View?, position: Int)
}
Two ways you can have an access to recyclerview in kotlin is first you can directly declare OnClickListener in adapter and make redirection inside it and second way is you can redirect your onclick to fragment/activity where you have set adapter of recycler
1.
class CartAdapter(context: Context) : RecyclerView.Adapter<CartAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.layout_cart, parent, false));
}
override fun getItemCount(): Int {
return 10;
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.rtbProductRating.setOnClickListener{
var iNavigation= Intent(context,MainActivity::class.java)
iNavigation.flags= Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
context.startActivity(iNavigation)
// directly redirect your activity from adapter
}
}
class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView)
}
Second way you can have is redirect your adapter click to fragment/activity and then redirect your activity from there instead of redirecting from adapter
class CartAdapter(context: Context, onClickListener: View.OnClickListener) : RecyclerView.Adapter<CartAdapter.ViewHolder>() {
var context = context
var onClickListener = onClickListener
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.layout_cart, parent, false));
}
override fun getItemCount(): Int {
return 10;
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
//set your position to the view
holder.itemView.rtbProductRating.tag = position
//redirect click to the fragment
holder.itemView.rtbProductRating.setOnClickListener {
onClickListener.onClick(holder.itemView.rtbProductRating)
}
// holder.itemView.tv_waybill_count.text = holder.itemView.context.getString(R.string.waybills,5)
}
class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView)
}
Your fragment will look like:
class CartFragment: BaseFragment(),View.OnClickListener {
override val layout= R.layout.frg_cart
override fun onClick(v: View?) {
var position=v?.tag as Int
if(position==0){
var iNavigation= Intent(this,MainActivity::class.java)
iNavigation.flag=Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
startActivity(iNavigation)
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
listener()
}
private fun listener() {
cart_rv.adapter=CartAdapter(activity,this)
}
}
RecyclerItemClickListener
package com.mypackage.custom
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
#Suppress("DEPRECATION")
class RecyclerItemClickListener(context: Context, private val mListener: OnItemClickListener?) : RecyclerView.OnItemTouchListener {
private var mGestureDetector: GestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent): Boolean {
return true
}
})
interface OnItemClickListener {
fun onItemClick(view: View, position: Int)
}
override fun onInterceptTouchEvent(view: RecyclerView, e: MotionEvent): Boolean {
val childView = view.findChildViewUnder(e.x, e.y)
if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
mListener.onItemClick(childView, view.getChildPosition(childView))
return true
}
return false
}
override fun onTouchEvent(view: RecyclerView, motionEvent: MotionEvent) {}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
}
For Activity:
recyclerView!!.addOnItemTouchListener(
RecyclerItemClickListener(this!!, object : RecyclerItemClickListener.OnItemClickListener {
override fun onItemClick(view: View, position: Int) {
//Write your code here
}
})
For Fragment:
recyclerView!!.addOnItemTouchListener(
RecyclerItemClickListener(this!!.activity!!, object : RecyclerItemClickListener.OnItemClickListener {
override fun onItemClick(view: View, position: Int) {
//Write your code here
}
})
OH what the heck someone might like this
We all place edit and trashcan images in out recyclerview and would like something to happen when they are clicked. Here is our Kotlin example
This is in a card view that is inflated in the Adapter
<RelativeLayout
android:id="#+id/editCLICK"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginStart="370dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="12dp">
<ImageView
android:id="#+id/ivEdit"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="#color/color_Transparent"
android:src="#drawable/ic_edit"
android:tint="#color/color_lightBlue" />
</RelativeLayout>
then in the Adapter we do some binding
override fun onBindViewHolder(holder: ParentViewHolder, position: Int) {
val items = parentList[position]
holder.item.text = items.dept
holder.editCLICK.setOnClickListener {
val i = Intent(context, EnterParentActivity::class.java)
i.putExtra("FROM", "U")
i.putExtra("MainActId",items.idD)
i.putExtra("PFK",items.fkD)
i.putExtra("ET",items.dept)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(i)
}
}
inner class ParentViewHolder(view: View):RecyclerView.ViewHolder(view){
var item: TextView = view.findViewById(R.id.tvDept) as TextView
var editCLICK: RelativeLayout = view.findViewById(R.id.editCLICK) as RelativeLayout
}
Simple quick and reliable enjoy
I came up with this solution to open an activity when row is clicked, using a companion object and interface. The activity is opened from main activity since I had to save list state before leaving.
Adapter
class MyAdapter(
val dataList: List<objects.ListObject>, val listener: ItemClickListener
) : RecyclerView.Adapter<MyAdapter.ListViewHolder>()
{
companion object {
var mClickListener: ItemClickListener? = null
}
interface ItemClickListener
{
fun clickRow(position: Int)
}
override fun onBindViewHolder(holder: MyAdapter.ListViewHolder, position: Int)
{
holder.bindData(
...
)
mClickListener = listener
holder.itemView.setOnClickListener { view ->
mClickListener?.clickRow(position)
}
}
...
}
Main activity
val context = this
private lateinit var mMyAdapter: MyAdapter
fun initList()
{
mMyAdapter =
MyAdapter(dataList, object : MyAdapter.ItemClickListener
{
override fun clickRow(position: Int)
{
openActivityListItems(position)
}
}
)
}
fun openActivityListItems(position : Int)
{
recyclerViewState = mListView.getLayoutManager()?.onSaveInstanceState()
val intent = Intent(context, ListItems::class.java)
intent.putExtra("Parameter1", dataList[position].Parameter1)
intent.putExtra("Parameter2", dataList[position].Parameter2)
context.startActivity(intent)
}
Related
I am trying to implement the function onClick in the CustomAdapter into a button so that I can call an action. I want to make it so that when the user clicks on the recycler item, the onClick function calls openTopSheet() that is from MainActivity which brings down the top sheet. In essence, how can I make it so that onClick can perform a method from MainActivity? Any help would be appreciated
CustomAdapter.kt
class CustomAdapter(val modelList: List<Model>, val context: Context) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as ViewHolder).bind(modelList.get(position));
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return ViewHolder(layoutInflater.inflate(R.layout.row_item, parent, false))
}
override fun getItemCount(): Int {
return modelList.size;
}
lateinit var mClickListener: ClickListener
fun setOnItemClickListener(aClickListener: ClickListener) {
mClickListener = aClickListener
}
interface ClickListener {
fun onClick(pos: Int, aView: () -> Unit)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
var r = MainActivity()
init {
itemView.setOnClickListener(this)
}
override fun onClick(p0: View?) {
r.openTopSheet()
}
fun bind(model: Model): Unit {
itemView.txt.text = model.name
itemView.sub_txt.text = model.version
val id = context.resources.getIdentifier(model.name.toLowerCase(Locale.ROOT), "drawable", context.packageName)
itemView.img.setBackgroundResource(id)
}
}
}
MainActivity.kt
open class MainActivity : AppCompatActivity(), BottomSheetRecyclerViewAdapter.ListTappedListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home_page)
}
fun openTopSheet() {
topSheetBehavior.state = TopSheetBehavior.STATE_EXPANDED
topSheetBehavior.setTopSheetCallback(object : TopSheetBehavior.TopSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float, isOpening: Boolean?) {
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
}
})
}
}
In ViewHolder:
bind(model: Model, openTopSheet: ()->Unit)
itemView.button.setOnClickListener{
openTopSheet.invoke()
}
In adapter:
class CustomAdapter(
val modelList: List<Model>,
val context: Context,
val openTopSheet: ()->Unit)
)
(holder as ViewHolder).bind(modelList.get(position), openTopSheet);
In Activity:
val adapter = CustomAdapter(listOf(), this, { openTopSheet() } )
And you need to add openTopSheet method in activity.
So my main problem at the moment is trying to add data to my recycler view. Essentially I created a button that adds the string "hello" to a list and then tries to call mAdapter.notifyItemInserted(dataSet.size). It adds perfectly to the end of the list in the logs, but I just can't figure out how to update the UI for it.
The only items I can manage to load at the moment are the items fakeItems that are created when I start the app.
class MainActivity : AppCompatActivity() {
var dataSet = mutableListOf<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
insertFakeItems()
setUpRecycler()
}
private fun insertFakeItems() {
for (i in 0..5) {
dataSet.add("Item #$i")
}
}
private fun setUpRecycler() {
val mAdapter = MyAdapter(dataSet)
val mList = list
mList.layoutManager = LinearLayoutManager(this)
mList.adapter = mAdapter
mList.orientation =
DragDropSwipeRecyclerView.ListOrientation.VERTICAL_LIST_WITH_VERTICAL_DRAGGING
//mList.disableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.RIGHT)
// button
val onItemSwipeListener = object : OnItemSwipeListener<String> {
override fun onItemSwiped(
position: Int,
direction: OnItemSwipeListener.SwipeDirection,
item: String
): Boolean {
Log.d("Main", "Position = $position, Direction = $direction, Item = $item")
when (direction) {
OnItemSwipeListener.SwipeDirection.RIGHT_TO_LEFT -> {
Toast.makeText(applicationContext, "Item ${position+1} deleted", Toast.LENGTH_SHORT).show()
//todo: add deleted code here
}
OnItemSwipeListener.SwipeDirection.LEFT_TO_RIGHT -> {
Toast.makeText(applicationContext, "Item ${position+1} archived", Toast.LENGTH_SHORT).show()
//todo: add archived code here
}
}
return false
}
}
mList.swipeListener = onItemSwipeListener
fab_add.setOnClickListener {
Log.d("Main", "Button pressed")
dataSet.add(dataSet.size,"hello")
mAdapter.notifyItemInserted(dataSet.size)
println(dataSet)
}
}
}
And this is the adapter:
class MyAdapter(dataSet: MutableList<String>)
: DragDropSwipeAdapter<String, MyAdapter.ViewHolder>(dataSet) {
class ViewHolder(itemView: View) : DragDropSwipeAdapter.ViewHolder(itemView) {
val itemText: TextView = itemView.findViewById(R.id.item_text)
val dragIcon: ImageView = itemView.findViewById(R.id.drag_icon)
}
override fun getViewHolder(itemLayout: View) = MyAdapter.ViewHolder(itemLayout)
override fun onBindViewHolder(item: String, viewHolder: MyAdapter.ViewHolder, position: Int) {
viewHolder.itemText.text = item
}
override fun getViewToTouchToStartDraggingItem(item: String, viewHolder: MyAdapter.ViewHolder, position: Int): View? {
return viewHolder.dragIcon
}
override fun onDragFinished(item: String, viewHolder: ViewHolder) {
super.onDragFinished(item, viewHolder)
println("$dataSet")
}
}
The problem is that in Java or Kotlin you pass by value not by reference. So until you change the value of the dataSet you are using in RecyclerView, you won't get any effect.
Do something like this (:
class MyAdapter(dataSet: MutableList<String>)
: DragDropSwipeAdapter<String, MyAdapter.ViewHolder>(dataSet) {
private var dataSet: MutableList<String>
init {
this.dataSet = dataSet
}
class ViewHolder(itemView: View) : DragDropSwipeAdapter.ViewHolder(itemView) {
val itemText: TextView = itemView.findViewById(R.id.item_text)
val dragIcon: ImageView = itemView.findViewById(R.id.drag_icon)
}
override fun getViewHolder(itemLayout: View) = MyAdapter.ViewHolder(itemLayout)
override fun onBindViewHolder(item: String, viewHolder: MyAdapter.ViewHolder, position: Int) {
viewHolder.itemText.text = item
}
override fun getViewToTouchToStartDraggingItem(item: String, viewHolder: MyAdapter.ViewHolder, position: Int): View? {
return viewHolder.dragIcon
}
override fun onDragFinished(item: String, viewHolder: ViewHolder) {
super.onDragFinished(item, viewHolder)
println("$dataSet")
}
fun updateItem(item: String) {
this.dataSet.add(item)
notifyItemInserted(dataSet.size - 1)
}
}
In activity:
fab_add.setOnClickListener {
Log.d("Main", "Button pressed")
mAdapter.updateItem("hello")
println(dataSet)
}
You must call notifyDatasetChanged after changing recycleView items list.
I have used Kotlin for implementing click listeners
How to properly use it, currently click is not detecting, I am not able to detect click using below code.
class AdptEvents (val items: MutableList<TestModel>, val context: Context) : RecyclerView.Adapter<ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, p1: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.row_event, parent, false))
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder?.tvAnimalType?.text = items.get(position).getName()
holder?.rootView.setOnClickListener{
clickEventRow(items,position)
true
}
holder?.rootView.setOnLongClickListener {
clickEventRow(items,position)
true
}
}
private fun clickEventRow( items: MutableList<TestModel>, position: Int ) {
Toast.makeText(context,items[position].getName(),Toast.LENGTH_LONG).show()
}
}
class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
// Holds the TextView that will add each animal to
val tvAnimalType = view.txtTitle!!
val rootView = view.eventListRootId!!
}
You dont need anything else
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder?.tvAnimalType?.text = items.get(position).getName()
holder?.rootView.setOnClickListener{
// code here
}
holder?.rootView.setOnLongClickListener {
//code here
}
}
Change your view holder. Also, if you need to get index, use adapterPosition variable.
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
// Holds the TextView that will add each animal to
val tvAnimalType = itemView.txtTitle!!
val rootView = itemView.eventListRootId!!
init {
itemView.setOnClickListener {
Toast.makeText(context, "OnClick", Toast.LENGTH_LONG).show()
}
// OnLongClick and etc.
}
}
If you need to handle root view click events apply it dirrectly
class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
// Holds the TextView that will add each animal to
val tvAnimalType = view.txtTitle!!
view.setOnClickListener {
Toast.makeText(view.context, "OnClick", Toast.LENGTH_LONG).show()
}
view.setOnLongClickListener {
Toast.makeText(view.context, "OnLongClick", Toast.LENGTH_LONG).show()
true
}
}
In my RecyclerView I need to implement a long touch listener for trigger some logic when a user press on a item of the list.
I code in Kotlin.
First, to do that I create this interface
interface TouchListener {
fun onLongTouch(view: View, position: Int)
}
Then I implement the RecyclerView.OnItemTouchListener
class RecyclerTouchListener(context: Context, recycleView: RecyclerView , private val touchListener: TouchListener?) : RecyclerView.OnItemTouchListener {
private var gestureDetector: GestureDetectorCompat? = null
init {
gestureDetector = GestureDetectorCompat(context, object : SimpleOnGestureListener() {
override fun onDown(event: MotionEvent): Boolean {
//this code is never reached
Log.i("Gestures", "onDown !")
return true
}
override fun onLongPress(e: MotionEvent?) {
//this code is never reached
Log.i("Gestures", "onLongPress !")
val child = if (e != null) recycleView.findChildViewUnder(e.x, e.y) else return
touchListener?.onLongTouch(child, recycleView.getChildAdapterPosition(child))
}
})
}
override fun onInterceptTouchEvent(rv: RecyclerView?, e: MotionEvent?): Boolean {
return false
}
//mandatory implementations
override fun onTouchEvent(rv: RecyclerView?, e: MotionEvent?) {}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
}
Here is my RecyclerView's Adapter
class TaskAdapter(tasks: MutableList<Task> = ArrayList()) : RecyclerView.Adapter<TaskAdapter.TaskViewHolder>() {
var tasks: MutableList<Task> = tasks
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): TaskViewHolder {
val context = parent?.context
val view = LayoutInflater.from(context)?.inflate(R.layout.list_item_task, parent, false)
return TaskViewHolder(view)
}
override fun getItemCount(): Int {
return tasks.size
}
override fun onBindViewHolder(holder: TaskViewHolder?, position: Int) {
holder?.bindTask(tasks[position])
}
fun addTask(task: Task) {
tasks.add(task)
notifyDataSetChanged()
}
fun removeTask(task: Task) {
tasks.remove(task)
notifyDataSetChanged()
}
inner class TaskViewHolder(view: View?) : RecyclerView.ViewHolder(view) {
private val descriptionTextView = view?.findViewById(R.id.task_description) as TextView
private val completedCheckBox = view?.findViewById(R.id.task_completed) as CheckBox
fun bindTask(task: Task) {
descriptionTextView.text = task.description
completedCheckBox.isChecked = task.completed
completedCheckBox.setOnCheckedChangeListener { _, isChecked ->
tasks[adapterPosition].completed = isChecked
}
}
}
}
So now in my MainActivity I call the recyclerView.addOnItemTouchListener() to set my custom RecyclerTouchListener and implement my own TouchListener interface.
class MainActivity : AppCompatActivity() {
private var adapter: TaskAdapter? = TaskAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.task_list)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.addOnItemTouchListener(RecyclerTouchListener(this, recyclerView, object : TouchListener {
override fun onLongTouch(view: View, position: Int) {
//again this code is never reached
Log.i("Gestures", "onLongPress !")
Toast.makeText(this#MainActivity, "Long touch on position :"+position,
Toast.LENGTH_SHORT).show()
}
}))
recyclerView.adapter = adapter
}
The problem is this code compile and run perfectly but the long press listener is never triggered. And I'm totally stuck on it. Where i'm wrong?
UPDATE
My first recommendation which I still stick with by would be to build off Dan's answer (but in Kotlin) since you could just add an OnLongClickListener via your bindTask method, but to simplify your code I think your just missing the proper return from onInterceptTouchEvent:
override fun onInterceptTouchEvent(rv: RecyclerView?, e: MotionEvent?): Boolean {
return gestureDetector?.onTouchEvent(e) ?: false
}
returning false here will cause the Touches to not get intercepted.
Example using OnLongClickListener:
First: Update your TaskAdapter code to take in a Listener in the constructor
class TaskAdapter(val taskListener: TaskListener, tasks: MutableList<Task> = ArrayList()) : RecyclerView.Adapter<TaskAdapter.TaskViewHolder>() {
var tasks: MutableList<Task> = tasks
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): TaskViewHolder {
val context = parent?.context
val view = LayoutInflater.from(context)?.inflate(R.layout.list_item_task, parent, false)
return TaskViewHolder(view)
}
override fun getItemCount(): Int {
return tasks.size
}
override fun onBindViewHolder(holder: TaskViewHolder?, position: Int) {
holder?.bindTask(tasks[position])
}
fun addTask(task: Task) {
tasks.add(task)
notifyDataSetChanged()
}
fun removeTask(task: Task) {
tasks.remove(task)
notifyDataSetChanged()
}
inner class TaskViewHolder(view: View?) : RecyclerView.ViewHolder(view) {
private val descriptionTextView = view?.findViewById(R.id.task_description) as TextView
private val completedCheckBox = view?.findViewById(R.id.task_completed) as CheckBox
private val parentView = view
fun bindTask(task: Task) {
descriptionTextView.text = task.description
completedCheckBox.isChecked = task.completed
completedCheckBox.setOnCheckedChangeListener { _, isChecked ->
tasks[adapterPosition].completed = isChecked
}
parentView?.setOnLongClickListener({
view: View? ->
// the callback to the activity
taskListener.onLongPressed(view, task)
true
})
}
}
}
Second: Create the interface to implement in your Activity:
interface TaskListener{
fun onLongPressed(view: View?, task: Task)
}
Third: Usage
class MainActivity : AppCompatActivity(), TaskListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adapter = TaskAdapter(this) // 'this' is the TaskListener
//... other onCreate Code
}
// interface method to intercept the long click
override fun onLongPressed(view: View?, task: Task) {
Log.d("TAG", "Item longPressed! ${task.description}")
}
}
Good Luck and Happy coding!
This is in Java, but shouldn't you be able to just pass a listener into your ViewHolders when you create them and use a long click listener to detect the long touch?
class CustomViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener {
private TouchListener listener;
CustomViewHolder(View itemView, TouchListener listener) {
super(itemView);
itemView.setOnLongClickListener(this);
}
#Override
public boolean onLongClick(View view) {
listener.onLongTouch(view, getAdapterPosition());
return true;
}
}
You'd also need another callback from your adapter to your Activity but that should be pretty straightforward.
How can I create on click event using interface?
In my application I've created view click interface to detect clicking on adapter items into parent activity. After creating interface and method into adapter how I can use this interface to call the view listener ?
Please check this code, It's working fine for me.
First Create Adapter class.
class ChapterAdapter(private val activity: Activity, val mWords: ArrayList<Chapter>, val btnlistener: BtnClickListener) : RecyclerView.Adapter<ChapterAdapter.ViewHolder>() {
companion object {
var mClickListener: BtnClickListener? = null
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return ViewHolder(layoutInflater.inflate(R.layout.layout_capter_raw, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
mClickListener = btnlistener
val item = mWords[position]
holder.layout_chapter_name.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
if (mClickListener != null)
mClickListener?.onBtnClick(position)
}
})
}
override fun getItemCount(): Int {
return mWords.size
}
override fun getItemId(position: Int): Long {
return super.getItemId(position)
}
override fun getItemViewType(position: Int): Int {
return super.getItemViewType(position)
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val txt_capter_name = view.txt_capter_name
}
open interface BtnClickListener {
fun onBtnClick(position: Int)
}
}
After create and declare adapter in your Activity or Fragment.
listAdapter = ChapterAdapter(activity, _arrChapterList, object : ChapterAdapter.BtnClickListener {
override fun onBtnClick(position: Int, chapter_id: String, chapter_size: String, chapter_name: String) {
toast(chapter_id + " = " + chapter_size, Toast.LENGTH_LONG)
}
})
In Kotlin the proper way doing this, is using callbacks instead of Java Interfaces. Example:
class MyAdapter(private val callback: (YourModel) -> Unit) {
override fun onBindViewHolder(holder: DataBoundViewHolder<YourModel>, position: Int) {
bind(holder.binding, items!![position])
holder.binding.executePendingBindings()
holder.binding.root.setOnClickListener { callback(binding.model) }
}
}
And create the adapter somewhere using
MyAdapter myAdapter = MyAdapter( { println{"Clicked $it"} })
Edit: Since the Asker would like to see a full working code i used the code from Sumit and replaced the Interfaces with Kotlin-Callbacks.
class ChapterAdapter(private val activity: Activity,
val mWords: ArrayList<Chapter>,
val callback: (Any) -> Unit) :
RecyclerView.Adapter<ChapterAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return ViewHolder(layoutInflater.inflate(R.layout.layout_capter_raw, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
val item = mWords[position]
holder.layout_chapter_name.setOnClickListener( callback {$it})
}
override fun getItemCount(): Int = mWords.size
override fun getItemId(position: Int): Long = super.getItemId(position)
override fun getItemViewType(position: Int): Int = super.getItemViewType(position)
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val txt_capter_name = view.txt_capter_name
}
and finally creating the Adapter
listAdapter = ChapterAdapter(activity, _arrChapterList, {
toast( "Clicked $it", Toast.LENGTH_LONG)
})
I have the same problem, and here is my solution:
package adapter
class MarketplaceListAdapter : RecyclerView.Adapter<MarketplaceListAdapter.ViewHolder> {
private val marketplaceList: ArrayList<Marketplace>
constructor(marketplaceList: ArrayList<Marketplace>) : super() {
this.marketplaceList = marketplaceList
}
private var listener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// implementation
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// implementation
}
override fun getItemId(position: Int): Long {
return 0
}
override fun getItemCount(): Int {
return marketplaceList.size
}
fun setListener(listener: OnItemClickListener) {
this.listener = listener
}
interface OnItemClickListener {
fun onItemClick(marketplace: Marketplace)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
var tvTitle: TextView = itemView.findViewById(R.id.tvTitle)
var ivIcon: ImageView = itemView.findViewById(R.id.ivIcon)
init {
itemView.setOnClickListener(this)
}
override fun onClick(v: View) {
listener?.onItemClick(marketplaceList[adapterPosition])
}
}
}
And on my fragment:
val list = view.findViewById<RecyclerView>(R.id.list)
list?.let {
adapter?.let { adapter ->
list.adapter = adapter
adapter.setListener(object : MarketplaceListAdapter.OnItemClickListener {
override fun onItemClick(marketplace: Marketplace) {
onMarketplaceSelected(marketplace)
}
})
}
}