I'm trying to create a RecyclerView adapter with two types of items. First one is Header, second one is child item. And when user tap on Header item it will expand child items. Next tap will collapse current section of items. But when I tap on Header in my RecyclerView it shows expanded items after second header. I don't know how to fix it
class SectionedAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var items = mutableListOf<ListItem>()
fun setItems(items: MutableList<ListItem>) {
this.items = items
notifyDataSetChanged()
}
class HeaderItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: HeaderItem) = with(itemView) {
textViewHeader.text = item.title
}
}
class TextItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: TextItem) = with(itemView) {
textViewTitle.text = item.title
textViewContent.text = item.content
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType) {
TYPE_HEADER -> {
val headerView = LayoutInflater.from(parent.context).inflate(R.layout.item_header, parent, false)
return HeaderItemViewHolder(headerView)
}
TYPE_TEXT -> {
val textView = LayoutInflater.from(parent.context).inflate(R.layout.item_text, parent, false)
return TextItemViewHolder(textView)
}
else -> throw IllegalArgumentException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = items[position]
when(holder) {
is HeaderItemViewHolder -> {
holder.bind(item as HeaderItem)
holder.itemView.setOnClickListener {
if(item.expanded) {
items.addAll(item.items)
notifyItemRangeInserted(position, item.items.size)
}
else {
items.removeAll(item.items)
notifyItemRangeRemoved(position, item.items.size)
}
item.expanded = !item.expanded
}
}
is TextItemViewHolder -> {
holder.bind(item as TextItem)
}
else -> throw IllegalArgumentException()
}
}
override fun getItemCount(): Int {
return items.count()
}
override fun getItemViewType(position: Int): Int {
val item = items[position]
return when(item) {
is HeaderItem -> TYPE_HEADER
is TextItem -> TYPE_TEXT
else -> throw IllegalArgumentException()
}
}
companion object {
const val TYPE_HEADER = 0
const val TYPE_TEXT = 1
}
}
I think what you are trying to achieve would be easier to do with nested Recyclerviews.
In your item_header.xml add a recyclerview, in onBindViewHolder, initialize this subRecyclerView for every sublist, initialize its adapter etc, populate this sub recyclerview with your required items, and let your onclick listener expand / collapse this sub recyclerView by setting its visibility to View.GONE (parent views layout height needs to be set to wrap_content for this to work)
Related
I have a list of item in RecylerView, with have a delete button with delete the item from the list.
When I click the delete button, the item at last got deleted not the item selected to be deleted.
What's more, it creates a new item with old item's data instead of new data.
The following below is the code written in Kotlin:
class MedicineAdapter(private val activity: Activity, private val dataSet: MutableList<MedicineModel>, private val medicineSet: List<MedicineData>): RecyclerView.Adapter<MedicineAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val deleteLayout: LinearLayout
val medicineLayout: LinearLayout
val medicineName: TextView
val timeLayout: LinearLayout
val medicineTime: TextView
init {
deleteLayout = view.findViewById(R.id.delete_layout)
medicineLayout = view.findViewById(R.id.medicine_layout)
medicineName = view.findViewById(R.id.medicine_name)
timeLayout = view.findViewById(R.id.time_layout)
medicineTime = view.findViewById(R.id.medicine_time)
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.layout_medicine_item, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.deleteLayout.setOnClickListener {
deleteRecord(viewHolder.adapterPosition)
}
viewHolder.medicineLayout.setOnClickListener {
val spinnerDialog = SpinnerDialog(activity, medicineSet.map { p -> p.name } as ArrayList<String>, "请选择药品", "关闭")
spinnerDialog.setCancellable(true)
spinnerDialog.setShowKeyboard(false)
spinnerDialog.bindOnSpinerListener { item, position ->
viewHolder.medicineName.text = item
}
spinnerDialog.showSpinerDialog()
}
}
override fun getItemCount(): Int {
return dataSet.count()
}
fun addRecord() {
val position = dataSet.count()
dataSet.add(position, MedicineModel())
notifyItemInserted(position)
}
#SuppressLint("NotifyDataSetChanged")
private fun deleteRecord(position: Int) {
dataSet.removeAt(position)
notifyItemRemoved(position)
notifyDataSetChanged()
}
I have called notifyDataSetChanged however it does not seems to work.
I have one ParentAdapter that has multiple view holders and each view holder has its own RecyclerView and an adapter.
I am facing a problem when I need to notify an item of a child adapter is changed. I passed the position of the changed child view holder to the parent and I can use notifyItemChanged(position) but the whole child adapter is recreated.
What I need is to be able to notify a child item and keep the state of the child recyclerview as it is.
Here's the code of the Parent Adapter:
class ParentAdapter(): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private lateinit var context: Context
private var items = listOf<ParentItem>()
companion object {
const val CHILD_ONE = 1
const val CHILD_TWO = 2
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
context = parent.context
val layoutInflater = LayoutInflater.from(context)
return when(viewType) {
CHILD_ONE -> {
ChildOneViewHolder(layoutInflater.inflate(R.layout.child_one, parent, false))
}
CHILD_TWO -> {
ChildTwoViewHolder(layoutInflater.inflate(R.layout.child_two, parent, false))
}
else -> {
BlankItemViewHolder(layoutInflater.inflate(R.layout.fragment_blank, parent, false))
}
}
}
fun update(items: List<Section>) {
this.items = items
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int {
val item = items[position]
return when(item.type) {
1 -> CHILD_ONE
2 -> CHILD_TWO
else -> -1
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder.itemViewType) {
CHILD_ONE -> {
val section = items[position]
val childOneAdapter = ChildOneAdapter(this)
if(!section.items.isNullOrEmpty()) {
holder.rvChildOne.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = childOneAdapter
isNestedScrollingEnabled = true
}
childOneAdapter.update(section.items)
}
}
CHILD_TWO -> {
val section = items[position]
val childTwoAdapter = ChildTwoAdapter(this)
if(!section.items.isNullOrEmpty()) {
holder.rvChildTwo.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = childTwoAdapter
isNestedScrollingEnabled = true
}
childTwoAdapter.update(section.items)
}
}
else ->
initBlankSection(holder as BlankItemViewHolder, position)
}
}
private fun initBlankSection(holder: BlankItemViewHolder, position: Int) {
}
private class ChildOneViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val rvChildOne: RecyclerView = itemView.findViewById(R.id.rv_child_one)
}
private class ChildTwoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val rvChildTwo: RecyclerView = itemView.findViewById(R.id.rv_child_two)
}
private class BlankItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {}
override fun getItemCount(): Int = items.size
}
i want to get item from checked checkbox in my recyclerview item, this my adapter
class SelectedListDateAdapter(var listDate: List<DateDay>, private val onItemCheckListener: OnItemCheckListener) :
RecyclerView.Adapter<SelectedListDateAdapter.SelectedListDateViewHolder>() {
lateinit var binding: ItemCheckBoxDateBinding
inner class SelectedListDateViewHolder(item: ItemCheckBoxDateBinding) : RecyclerView.ViewHolder(item.root) {
val checkBoxList = item.checkBox
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SelectedListDateViewHolder {
binding = ItemCheckBoxDateBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return SelectedListDateViewHolder(binding)
}
override fun onBindViewHolder(holder: SelectedListDateViewHolder, position: Int) {
holder.checkBoxList.setOnCheckedChangeListener(null)
holder.checkBoxList.isChecked = listDate[position].isSelected
holder.itemView.apply {
val currentItem = listDate[position]
binding.tvDateList.text = listDate[position].date
setOnClickListener {
binding.checkBox.isChecked = !binding.checkBox.isChecked
if (binding.checkBox.isChecked) {
binding.checkBox.setOnCheckedChangeListener { buttonView, isChecked ->
currentItem.isSelected = isChecked
}
onItemCheckListener.onItemCheck(currentItem)
} else {
binding.checkBox.setOnCheckedChangeListener { buttonView, isChecked ->
currentItem.isSelected = isChecked
}
onItemCheckListener.onItemUncheck(currentItem)
}
}
}
}
override fun getItemCount(): Int {
return listDate.size
}
}
im referring to this question get list of checked item to make that adapter
yes, it get the item and remove them but everytime i click an item in recyclerview it always check and uncheck the last item
i have checking this question CheckBox in RecyclerView keeps on checking different items but my result still the same, any help is appreciated
Maybe viewHolder reuse the previous item.Try to update listData, not currentItem.
And move the nested Listener
override fun onBindViewHolder(holder: SelectedListDateViewHolder, position: Int) {
holder.itemView.tvDateList.text = listDate[position].date
holder.checkBoxList.isChecked = listDate[position].isChecked
holder.checkBoxList.setOnClickListener {
listDate[position].isSelected = holder.checkBoxList.isChecked
}
holder.itemView.setOnClickListener {
holder.checkBoxList.isChecked = !holder.checkBoxList.isChecked
listDate[position].isSelected = holder.checkBoxList.isChecked
val currentItem = listDate[position]
if (holder.checkBoxList.isChecked) {
onItemCheckListener.onItemCheck(currentItem)
} else {
onItemCheckListener.onItemUncheck(currentItem)
}
}
}
after getting successful response, Header added to first position of recyclerview but first item of list not showing.
list having 5 items but its shows only last 4 items.
how to show all list items with extra added header in recyclerview.
class AllCategoryAdapter(val categoryList : List<AllCategoryBean>) : RecyclerView.Adapter<RecyclerView.ViewHolder>()
{
private val TYPE_HEADER : Int = 0
private val TYPE_LIST : Int = 1
override fun getItemViewType(position: Int): Int {
if(position == 0)
{
return TYPE_HEADER
}
return TYPE_LIST
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if(viewType == TYPE_HEADER)
{
val header = LayoutInflater.from(parent.context).inflate(R.layout.cv_all_category_header,parent,false)
return ViewHolderHeader(header)
}
val header = LayoutInflater.from(parent.context).inflate(R.layout.cv_all_category,parent,false)
return ViewHolder(header)
}
override fun getItemCount(): Int {
return categoryList.size + 1
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val listItem : AllCategoryBean = categoryList[position]
if(holder is ViewHolderHeader)
{
holder.tvCategoyName.setText("All Category")
}
if(holder is ViewHolder)
{
holder.tvCategoyName.setText(listItem.getCategoryName())
}
}
class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView)
{
val tvCategoyName = itemView.findViewById(R.id.tvCategoyName) as TextView
}
class ViewHolderHeader(itemView : View) : RecyclerView.ViewHolder(itemView)
{
val tvCategoyName = itemView.findViewById(R.id.tvCategoyName) as TextView
}
}
As #hiddeneyes02 commented in your question, you should be getting ArrayIndexOutOfBoundsException.
BTW when you increased categoryList size by override fun getItemCount(), you must also decrease position by one for getting related item in your list when holder is instance of your view, not header.
So your onBindViewHolder must look like this:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder instanceof ViewHolderHeader) {
holder.tvCategoyName.setText("All Category")
} else if (holder instanceof ViewHolder) {
val listItem : AllCategoryBean = categoryList[position - 1]
holder.tvCategoyName.setText(listItem.getCategoryName())
}
How can we mark single item is selected in Recyclerview using kotlin. When I select an item and after that click on other item then previously selected item should be dis-selected.Here is my adapter class in kotlin:..
class ListAdapter(var context: Context, var list: ArrayList<ListModel>) : RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MyViewHolder {
val v = LayoutInflater.from(parent?.context).inflate(R.layout.list_item, parent, false)
return MyViewHolder(v)
}
override fun getItemCount(): Int {
return list.size
}
override fun onBindViewHolder(holder: MyViewHolder?, position: Int) {
holder?.bindItems(list[position])
}
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view){
fun bindItems(items: ListModel) {
itemView.txt_que.text = items.que
itemView.txt_ans.text = items.ans
itemView.txt_sr_no.text = items.srNo
}
}`
Here dataItem is Model Class and please take one extra Boolean variable isSelected in model class (default value is false) and true when select item then true this variable on selected position,
below are example :
class AllModuleListAdapter(
var context: Context?,
private var moduleList: ArrayList<DataItem?>?,
private var mCallback: DeviceClickListener
) :
RecyclerView.Adapter<AllModuleListAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val context = parent.context
val itemView = LayoutInflater.from(context).inflate(R.layout.item_modules, parent, false)
return MyViewHolder(itemView)
}
override fun getItemCount(): Int {
return moduleList?.size ?: 0
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(moduleList?.get(position))
if (moduleList?.get(position) is DataItem) {
val dataItem = moduleList?.get(position) as DataItem
if (dataItem.isSelected) {
context?.let { ContextCompat.getColor(it, R.color.colorOrange) }
?.let { holder.itemView.setBackgroundColor(it) }
} else {
context?.let { ContextCompat.getColor(it, R.color.white) }
?.let { holder.itemView.setBackgroundColor(it) }
}
}
}
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
itemView.constraint_main.setOnClickListener {
val list = moduleList as List<DataItem>
for (item in list.indices) {
list[item].isSelected = false
}
list[adapterPosition].isSelected = true
mCallback.onDeviceClick(adapterPosition)
notifyDataSetChanged()
context?.let { it1 -> ContextCompat.getColor(it1, R.color.colorOrange) }?.let { it2 ->
itemView.constraint_main?.setBackgroundColor(it2)
}
}
}
fun bind(module: DataItem?) {
itemView.txtModuleName?.text = module?.id.toString()
itemView.txtSignal?.text = module?.room
}
}
}
if (mPosition == position)
{
//set selected here
} else
{
//set unselected here
}
holder.parentView.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view)
{
mPosition = position;
notifyDataSetChanged();
}
});
Write above code in onBindViewholder and declare mPosition as global int variable in adapter class
Try with this:- take one variable in your ListModel class as
var selected:boolean = false
then while setting the listModel items set this value as false as
for(int i=0;i<listModel.size;i++){
listModel.get(i).selected = false
}//this is for setting all values false
when you select any item from list call this method and then set selected = true for selected position and simply refresh the adapter list.
in your adapter check for this selected value and accordingly set the check box value inside your bindItems method
itemView.checkBox.selected = items.selected//this will set your checkbox selected value