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)
}
Related
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...
}
})
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 .
I'm using a RecyclerAdapter for the first time. I implemented it via a tutorial and it works fine so far. The way I'm using it is like a popup, so you click on a button and this button changes the opacity of the RecyclerView to 0.8 so it get's visible. Now I want to make the whole RecyclerView invisible after Clicking on an RecyclerItem. Cause the RecyclerView calls another Class "RecyclerAdapter" I'm wondering how I can address the RecyclerView there.
class RecyclerAdapter(private var titles: List<String>, private var details: List<String>, private var images:List<Int>) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>(){
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val itemTitle: TextView = itemView.findViewById(R.id.ks_title)
val itemDetail: TextView = itemView.findViewById(R.id.ks_descr)
val itemImage: ImageView = itemView.findViewById(R.id.ks_image)
init {
itemView.setOnClickListener { v: View ->
val position: Int = adapterPosition
Toast.makeText(
itemView.context,
"Kartensatz # ${position + 1} gewählt",
Toast.LENGTH_SHORT
).show()
SpielEinstellungen.kartensatz = adapterPosition + 1
// -> NEED TO CHANGE ALPHA OF RECYCLERVIEW TO 0 HERE )
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.kartensaetze,parent,false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return titles.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemTitle.text = titles[position]
holder.itemDetail.text = details[position]
holder.itemImage.setImageResource(images[position])
}
}
The best way is described in this answer: Is there a better way of getting a reference to the parent RecyclerView from the adapter?
lateinit var recycler: RecyclerView
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
recycler = recyclerView
}
You introduce a local variable in the RecyclerAdapter which is initialized when the onAttachedToRecyclerView is called, then you can use that.
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val itemTitle: TextView = itemView.findViewById(R.id.ks_title)
val itemDetail: TextView = itemView.findViewById(R.id.ks_descr)
val itemImage: ImageView = itemView.findViewById(R.id.ks_image)
init {
itemView.setOnClickListener {
Toast.makeText(itemView.context,
"Kartensatz # ${adapterPosition + 1} gewählt",
Toast.LENGTH_SHORT).show()
SpielEinstellungen.kartensatz = adapterPosition + 1
recycler.alpha(0f)
}
}
}
Imagine we have a simple list of items. Each item contains only a short title.
To handle the list we are using RecyclerView with ListAdapter and ViewHolders.
Each item/view is not editable unless we click it.
In this scenario I am using one view model for list and one for item under edit.
Unfortunately all my attempts failed.
I have tried to use two different view holders but the list was flickering, after all inflating view (in this case binding) is heavy.
Another shot I was giving to use the same view holder but with two various bind methods - one binding plain item, second binding with viewmodel instead of data object but it failed as well - suddenly a few rows were editable.
Has anyone solved it ?
class MistakesAdapter(private val editViewModel: MistakeEditViewModel) :
ListAdapter<Mistake, RecyclerView.ViewHolder>(MistakesDiffCallback()) {
companion object{
const val ITEM_PLAIN_VIEW_TYPE = 0
const val ITEM_EDITABLE_VIEW_TYPE = 1
}
private var itemPositionUnderEdit = -1
private val listener = object: MistakeItemListener{
override fun onClick(view: View, position: Int) {
Timber.d("OnClick : edit - $itemPositionUnderEdit, clickPos - $position")
editViewModel.onEditMistake(getItem(position))
itemPositionUnderEdit = position
notifyItemChanged(itemPositionUnderEdit)
}
}
override fun getItemViewType(position: Int) =
when (position) {
itemPositionUnderEdit -> ITEM_EDITABLE_VIEW_TYPE
else -> ITEM_PLAIN_VIEW_TYPE
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
when (viewType) {
ITEM_EDITABLE_VIEW_TYPE -> EditableMistakeViewHolder.from(parent)
else -> MistakeViewHolder.from(parent)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is EditableMistakeViewHolder -> holder.bind(editViewModel, listener)
is MistakeViewHolder -> holder.bind(getItem(position), listener)
else -> throw ClassCastException("Unknown view holder type")
}
}
class MistakeViewHolder private constructor(private val binding: ListItemMistakesBinding) :
RecyclerView.ViewHolder(binding.root) {
companion object {
fun from(viewGroup: ViewGroup): MistakeViewHolder {
val inflater = LayoutInflater.from(viewGroup.context)
val binding = ListItemMistakesBinding.inflate(inflater, viewGroup, false)
return MistakeViewHolder(binding)
}
}
fun bind(item: Mistake, listener: MistakeItemListener) {
binding.apply {
mistake = item
inputType = InputType.TYPE_NULL
this.listener = listener
position = adapterPosition
executePendingBindings()
}
}
}
class EditableMistakeViewHolder private constructor(private val binding: ListItemMistakesBinding)
: RecyclerView.ViewHolder(binding.root) {
companion object{
fun from(viewGroup: ViewGroup): EditableMistakeViewHolder {
val inflater = LayoutInflater.from(viewGroup.context)
val binding = ListItemMistakesBinding.inflate(inflater, viewGroup, false)
return EditableMistakeViewHolder(binding)
}
}
fun bind(viewModel: MistakeEditViewModel, listener: MistakeItemListener){
binding.apply {
this.viewModel = viewModel
inputType = InputType.TYPE_CLASS_TEXT
this.listener = listener
position = adapterPosition
root.setBackgroundColor(Color.GRAY)
}
}
}
}
class MistakeEditViewModel(private val repository: MistakesRepository) : ViewModel() {
#VisibleForTesting
var mistakeUnderEdit: Mistake? = null
//two-way binding
val mistakeName = MutableLiveData<String>()
fun onEditMistake(mistake: Mistake) {
mistakeUnderEdit = mistake
mistakeName.value = mistake.name
}
}
By changing my approach to the problem I solved it.
I make all list items editable but at the same time I am following focus.
To cut the long story short, I invoke item view model methods with help of OnFocusChangeListener and TextWatcher on my editTexts.
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?) {
...
}