I have implement a RecyclerView OnClickListener to open different new activity when we click on different item of the recycler View. I have doing all but it get one error in my Fragement :
verticalRecyclerView.adapter = MuscleAdapter(context, muscleList, OnGroupClickListener)
verticalRecyclerView.addItemDecoration(MuscleItemDecoration())
return view
In my code to find my recycler View, I get an error at the MuscleAdapter(context, muscleList, OnGroupClickListener), and it says : Classifier 'OnGroupClickListener' does not have a companion object, and thus must be initialized here
My Adapter looks that (it isn't finished but it contains no error message):
val context: MainActivity,
private val muscleList: ArrayList<MuscleModel>,
private val onGroupClickListener: OnGroupClickListener)
: RecyclerView.Adapter<MuscleAdapter.ViewHolder>(){
//box to stock components
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val muscleImage: ImageView = view.findViewById(R.id.image)
val muscleName: TextView = view.findViewById(R.id.name_item)
val muscleDescription: TextView = view.findViewById(R.id.description_item)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_muscular_group, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// stocks values of "muscle group"
val currentItem = muscleList[position]
//update values of the Muscle Group
holder.muscleName.text = currentItem.name
holder.muscleDescription.text = currentItem.description
holder.muscleImage.setImageResource(currentItem.drawable)
//window open when click ?
holder.itemView.setOnClickListener{
onGroupClickListener.onGroupItemClicked(position)
if (position == 0) {
CommentairePopup(this).show()
} else if (position == 1) {
} else if (position == 2) {
} else if (position == 3) {
} else if (position == 4) {
} else if (position == 5) {
} else if (position == 6) {
}
}
}
override fun getItemCount(): Int = muscleList.size
}
EDIT :
that is my OnGroupClickListener sorry, and it is an interface :
fun onGroupItemClicked(position : Int)
}
How can I suppress the error message from my Fragment ?
Thanks very much for any help !
What you did is incorrect because you're not specifying an instance of an object that implements the OnGroupClickListener interface, but rather, just the interface's name itself.
Here's a potential fix:
verticalRecyclerView.adapter = MuscleAdapter(context, muscleList, object : OnGroupClickListener {
override fun onGroupClick(parent: ExpandableListView!, v: View!, groupPosition: Int, id: Long) {
//Your implementation here...
}
})
Related
I'm trying to figure out how to get access to the views in my recyclerView. I want to be able to expand items in the recyclerView. When one expands, all the others should collapse.
class CustomAdapter(private val mList: List<Card>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
class ViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) {
val textView: TextView = itemView.findViewById(R.id.questionView)
val categoryView: TextView = itemView.findViewById(R.id.category_view)
val solutionView: TextView = itemView.findViewById(R.id.solutionView)
val editButton: FloatingActionButton = itemView.findViewById(R.id.floatingActionButton)
}
// create new views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.items_view_design, parent, false)
return ViewHolder(view)
}
// binds the list items to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itemsViewModel = mList[position]
holder.textView.text = itemsViewModel.question
holder.categoryView.text = itemsViewModel.subjectCategory
holder.solutionView.text = itemsViewModel.solution
fun showHide(view:View) {
view.visibility = if (view.visibility == View.VISIBLE){
View.GONE
} else{
View.VISIBLE
}
}
fun show(view:View){
view.visibility =View.VISIBLE
}
fun hide(view:View){
view.visibility = View.GONE
}
holder.textView.setOnClickListener{
showHide(holder.solutionView)
showHide(holder.editButton)
}
//This is where I need help.
}'''
In pseudocode, what I want to say is:
for loop (items in mList)
items.solution = holder.solutionView
hide(solutionView)
But I can't figure out how to get access to holder.categoryView for an item that isn't the current item being bound.
As I understand you need a property to indicate that an item was chosen and use it to check whether to hide or show some elements of your views:
class CustomAdapter(private val mList: List<Card>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
private var selectedCard: Card? = null
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itemsViewModel = mList[position]
// Your code
if (selectedCard != null && selectedCard != itemsViewModel) {
showHide(holder.solutionView)
showHide(holder.editButton)
}
holder.textView.setOnClickListener{
if (itemsViewModel == selectedCard) {
selectedCard = null
} else {
selectedCard = itemsViewModel
}
notifyDataSetChanged() // It's not optimal, but the easiest way
}
}
}
Thanks for your help. I followed the link posted by Umesh RecyclerView expand/collapse items
Worked for me! This is a Kotlin translation of the answer he linked to:
global variables:
// private var previousExpandedPosition = -1
// private var mExpandedPosition = -1
var isExpanded: Boolean = (position == mExpandedPosition)
holder.details.setVisibility(if (isExpanded) View.VISIBLE else View.GONE)
holder.itemView.isActivated = isExpanded
if (isExpanded) previousExpandedPosition = position
holder.itemView.setOnClickListener {
mExpandedPosition = if (isExpanded) -1 else position
notifyItemChanged(previousExpandedPosition)
notifyItemChanged(position)
}
I manage to display the items stored in a room database in a recycler view with checkboxes and i want to store the checked items in a list, to store the checked items on a list I use setOnClickListener on the checkbox like the code below in adapter but the application stop when i click to display the list or even if the list displayed with success sometimes it stops when i click on the checkbox of an item (for information when i remove the listener the list is displayed well and I can click on the checkboxes and everything works well but the problem is when i add the listener to store the checked items).
class Adapter (val selectedFluxs : MutableList<Flux>) : RecyclerView.Adapter<Adapter.VH>(
) {
class VH(itemView: View) : RecyclerView.ViewHolder(itemView){
lateinit var feed : Flux
}
var allFluxs : List<Flux> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val v = LayoutInflater
.from(parent.getContext())
.inflate(R.layout.item_layout, parent, false)
val holder = VH( v )
v.check.setOnClickListener( ) {
// it as CheckBox
if( v.check.isChecked ){
selectedFluxs.add ( holder.feed )
}else{
selectedFluxs.remove( holder.feed )
}
}
return holder
}
override fun getItemCount(): Int {
return allFluxs.size
}
fun setFlux( allFlux : List<Flux> ) {
this.allFluxs = allFlux
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: VH, position: Int) {
holder.itemView.apply {
Source.text = allFluxs[position].source
Tag.text = allFluxs[position].tag
Url.text = allFluxs[position].adr
check.isChecked =
holder.feed in selectedFluxs
}
}
}
It's too early to use ViewHolder views before instantiating the ViewHolder instance, so Move the code of the listener from onCreateViewHolder to onBindViewHolder
So, your class code should be:
class Adapter (val selectedFluxs : MutableList<Flux>) : RecyclerView.Adapter<Adapter.VH>(
) {
class VH(itemView: View) : RecyclerView.ViewHolder(itemView){
lateinit var feed : Flux
}
var allFluxs : List<Flux> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
val v = LayoutInflater
.from(parent.getContext())
.inflate(R.layout.item_layout, parent, false)
val holder = VH( v )
}
return holder
}
override fun getItemCount(): Int {
return allFluxs.size
}
fun setFlux( allFlux : List<Flux> ) {
this.allFluxs = allFlux
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: VH, position: Int) {
holder.itemView.apply {
Source.text = allFluxs[position].source
Tag.text = allFluxs[position].tag
Url.text = allFluxs[position].adr
setBackgroundColor(
if (position % 2 == 0)
Color.argb(30,0,220,0)
else
Color.argb(30,0,0,220)
)
check.isChecked =
holder.feed in selectedFluxs
holder.check.setOnClickListener( ) {
// it as CheckBox
if( v.check.isChecked ){
selectedFluxs.add ( holder.feed )
}else{
selectedFluxs.remove( holder.feed )
}
}
}
}
I solved the problem a minute after posting the question, in fact the problem is that I did not initialize the variable : lateinit var feed: Flux of the Holder with : holder.feed = allFluxs[position] in the onBindViewHolder method . i did that and it works .
How can I implement different views in one recyclerViews in Kotlin ???
I want to create an application containing legal codes. My problem is that individual legal provisions are divided into chapters. And if I can create a progran that will display all the recipes for me, I don't really know how to put it in the recyclerView between the layout with specific legal provisions layout with information about the number and title of the chapter.
The code below still shows me the same view.
package pl.nynacode.naukapraw
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.cart_view_legal_name.view.*
import kotlinx.android.synthetic.main.chapter_layout.view.*
class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>(){
class MyViewHolder(val view: View, val view2: View):RecyclerView.ViewHolder(view) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater= LayoutInflater.from(parent.context);
val legalName = layoutInflater.inflate(R.layout.cart_view_legal_name, parent ,false);
val chapterName = layoutInflater.inflate(R.layout.chapter_layout, parent,false);
return MyViewHolder(legalName, chapterName);
}
override fun getItemCount(): Int {
return KodeksKarny.nrArticle.size;
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
when(position){
0->{
val chapter = holder.view2.tvChapterName;
chapter.setText(KodeksKarny.nrArticle[position])
}
else->{
val nrArticle = holder.view.nrArt;
val textArticle=holder.view.txtArt;
nrArticle.setText(KodeksKarny.nrArticle[position]);
textArticle.setText(KodeksKarny.txtArticle[position]);
// obsługa klikniecia na przycisk
nrArticle.setOnClickListener{
if (textArticle.visibility == View.GONE){
textArticle.visibility = View.VISIBLE
}else textArticle.visibility = View.GONE
}
}
}
}
}
I will add that I'm a beginner and I can't do much yet
RecyclerView.Adapter has a method called fun getItemViewType(position: Int): Int that returns the type of view on a given position.
Based on that function you can create different view holders or pass to the same view holder type different layouts (but avoid last one).
You simply need to override a function in your adapter and decide the type of an item at that position:
override fun getItemViewType(position: Int): Int {
val item = getItem(position)
// the code below is just an example.
val type = when (item) {
is Header -> HEADER_TYPE
is NotHeader -> NOT_HEADER_TYPE
}
return type
}
Where you could define these types? In companion object for example:
class YourAdapter: ... {
companion object {
private const val HEADER_TYPE = 0
private const val NOT_HEADER_TYPE = 1
}
...
}
Later in onCreateViewHolder and onBindViewHolder you can create different view holders and bind to those view holders the data you have.
class YourAdapter: ... {
companion object {
private const val HEADER_TYPE = 0
private const val NOT_HEADER_TYPE = 1
}
...
override fun getItemViewType(position: Int): Int {
val item = getItem(position)
// the code below is just an example.
val type = when (item) {
is Header -> HEADER_TYPE
is NotHeader -> NOT_HEADER_TYPE
}
return type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
if (viewType == HEADER_TYPE) {
// Here you create HeaderViewHolder
} else {
val layoutInflater= LayoutInflater.from(parent.context);
val legalName = layoutInflater.inflate(R.layout.cart_view_legal_name, parent ,false);
val chapterName = layoutInflater.inflate(R.layout.chapter_layout, parent,false);
return MyViewHolder(legalName, chapterName);
}
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val itemViewType = getItemViewType(position)
if (itemViewType == HEADER_TYPE) {
// cast MyViewHolder to HeaderViewHolder, for example
val header = viewHolder as HeaderViewHolder
header.headerTitle.text = ...
} else {
val nrArticle = holder.view.nrArt;
... other type
}
}
}
Here are the official tutorials on how to create an adapter with different types of views.
What I personally prefer is to implement abstract class BaseViewHolder: RecyclerView.ViewHolder that will be used as a generic type argument of your adapter implementation. This BaseViewHolder should have an abstract method, like abstract fun bind(data: YourDataType). The function will be implemented by view holders that will extend the BaseViewHolder class.
Also, as Kotlin provides us with sealed classes I prefer to create a sealed class and objects that extend from it to hold view holder types so when you implement your onCreateViewHolder method it could avoid else case. But that is just what I like and is not required in any way.
An example of sealed class + objects + onCreateViewHolder:
sealed class Types(val rawType: Int) {
object Header: Types(0)
object NotHeader: Types(1)
companion object {
fun from(rawType: Int) =
when (rawType) {
Header.rawType -> Header
NotHeader.rawType -> NotHeader
else -> throw RuntimeException("No such type")
}
}
}
class YourAdapter ... {
override fun getItemViewType(position: Int): Int {
val item = getItem(position)
// the code below is just an example.
val type = when (item) {
is Header -> Types.Header.rawType
is NotHeader -> Types.NotHeader.rawType
}
return type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder =
when (Types.from(viewType)) {
is Types.Header -> // return HeaderViewHolder
is Types.NotHeader -> // return NotHeaderViewHolder
}
}
I'm new to Android development (and Kotlin).
I'm trying to implement a RecyclerView (which works fine) and when I click on a specific row it opens a new activity (Intent).
However, whenever I've press/click on one of the rows, I'm only able to get the value "-1" returned.
I've tried a number of different approaches (you should see the number of tabs in my browser).
This seems like it should be a fairly straightforward occurrence for something as common as a RecyclerView, but for whatever reason I'm unable to get it working.
Here is my RecyclerView Adapter file:
class PNHLePlayerAdapter (val players : ArrayList<PNHLePlayer>, val context: Context) : RecyclerView.Adapter<ViewHolder>() {
var onItemClick: ((Int)->Unit) = {}
// Gets the number of items in the list
override fun getItemCount(): Int {
return players.size
}
// Inflates the item views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(context).inflate(
R.layout.pnhle_list_item,
parent,
false
)
val viewHolder = ViewHolder(itemView)
itemView.setOnClickListener {
onItemClick(viewHolder.adapterPosition)
}
return ViewHolder(itemView)
}
// Binds each item in the ArrayList to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvPlayerName?.text = players[position].Name
holder.tvPlayerRank?.text = position.toString()
holder.tvPNHLe?.text = players[position].PNHLe.toString()
holder.tvTeam?.text = players[position].Team
holder.ivLeague?.setImageResource(leagueImageID)
}
}
class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val linLayout = view.hor1LinearLayout
val ivTeam = view.teamImageView
val tvPlayerName = view.playerNameTextView
val tvPlayerRank = view.rankNumTextView
val tvPNHLe = view.pnhleTextView
val tvTeam = view.teamTextView
val ivLeague = view.leagueImageView
}
As you can see, there is a class property "onItemClick" which uses a lambda as the click callback.
I setOnClickListener in the onCreateViewHolder method after the view is inflated.
Next, in my Activity I add the list to my Adapter and set the call back.
However, every time I 'Toast' the position it is displayed as '-1'.
val adapter = PNHLePlayerAdapter(list, this)
adapter.onItemClick = { position ->
Toast.makeText(this, position.toString(),Toast.LENGTH_SHORT).show()
var intent = Intent(this, PlayerCardActivity::class.java)
//startActivity(intent)
}
rv_player_list.adapter = adapter
Perhaps I'm not thinking about this properly, but shouldn't the position represent the row number of the item out of the RecyclerView???
Ideally, I need to use the position so that I can obtain the correct item from the 'list' (ArrayList) so that I can pass information to my next Activity using the Intent
I found the issue.
Change this line in onCreateViewHolder:
return ViewHolder(itemView)
to this one:
return viewHolder
I would reorganize the adapter like this:
class PNHLePlayerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<Adapter.ViewHolder>() {
interface AdapterListener {
fun onItemSelected(position: Int?)
}
var players: List<Player> = listOf()
set(value) {
field = value
this.notifyDataSetChanged()
}
var listener: AdapterListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_car_selector, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount(): Int {
return brands.size
}
inner class ViewHolder(view: View): androidx.recyclerview.widget.RecyclerView.ViewHolder(view) {
private var position: Int? = null
private val baseView: LinearLayout? = view.findViewById(R.id.baseView) as LinearLayout?
...
init {
baseView?.setOnClickListener {
listener?.onManufacturerSelected(position)
}
}
fun bind(position: Int) {
this.position = position
...
}
}
}
And from your activity/fragment set the listener as adapter.listener = this, and implement the onItemSelected(position: Int?)
override fun onItemSelected(position: Int?) {
...
}
I'm trying to make 2 view holder one will be at position 0 always which will be a header view for selecting image, another for displaying the model,
I can do it for normal recyclerview adapter but in firestore recycler adapter i can't do it,
what I try it increase the item count by 1 but I got
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
This is my adapter class code:
val TYPE_SELECT_IMAGE = 0
val TYPE_ITEMS = 1
class UsersImageAdapter(options: FirestoreRecyclerOptions<UserImage>)
: FirestoreRecyclerAdapter<UserImage, RecyclerView.ViewHolder>(options) , AnkoLogger{
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, model: UserImage) {
when(holder){
is SelectImageViewHolder -> {
holder.bindModel()
}
is UserImageViewHolder -> {
holder.bindImages(model)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
TYPE_SELECT_IMAGE -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_add_image, parent, false)
SelectImageViewHolder(view)
}
TYPE_ITEMS -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_image,parent,false)
UserImageViewHolder(view)
}
else -> throw RuntimeException()
}
}
override fun getItemViewType(position: Int): Int {
return if (isPositionHeader(position)) {
TYPE_SELECT_IMAGE
} else {
TYPE_ITEMS
}
}
fun isPositionHeader(position: Int): Boolean {
return position == 0
}
override fun getItemCount(): Int {
return super.getItemCount() + 1
}
}
and this is two view holder one for header view another for displaying model
class SelectImageViewHolder(val view: View) : RecyclerView.ViewHolder(view) , AnkoLogger{
fun bindModel() {
view.setOnClickListener {
info {
"clicked !"
}
}
}
}
class UserImageViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bindImages(userImage: UserImage) {
val imageView = view.findViewById<ImageView>(R.id.image_user_post)
Glide.with(view.context).load(userImage.imageUrl).into(imageView)
}
}
my goal is to keep the SelectImageViewHolder in position 0 always
FirebaseUI maintainer here. onBindViewHolder(RecyclerView.ViewHolder, Int, Model) makes a getItem(position) call internally to seamlessly populate your model. Since you're manually injecting an item, the RecyclerView is going to think there are items in the database and will blow up as you saw. To fix that, you'll want to override the native onBindViewHolder(RecyclerView.ViewHolder, Int) method without calling super:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder) {
is SelectImageViewHolder -> holder.bindModel()
is UserImageViewHolder -> {
// Manually get the model item
holder.bindImages(getItem(position - 1))
}
}
}