Search code examples
androidandroid-recyclerviewandroid-tablayout

How to scroll with smoothScrollToPosition of RecyclerView with dynamic position in recyclerview?


I use tabLayout as a header for recyclerView adapter. I have no problem for scroll to static position of recyclerview Items. for example position 2 for pizza header.tab zero for pizza.

override fun onTabSelected(tab: TabLayout.Tab?) {
if (tab.position == 0)
recyclerview.smoothScrollToPosition(2)}

The problem is that I don't know how to scroll to position of some headers items that I don't have their positions. as I know I can get all Item positions until they are visible in screen by onBindViewHolder function. right. But when all items are not visible or I am in the first of list , how could I get the rest of headers positions? for example drinks header position is in end of the list and I need to know position when user clicked on drink tab to go to. It seems is not good approach.

my scenario is that , I have a recyclerview adapter. It has two item types.

1-Header items type (Food type like: Sandwich - Pizza - ... )

2-Item type (Food like: Neapolitan Pizza - Chicago Pizza - ...)

my approach was : I create a hashmap . tried to fill it with just Header Items. <Name, Position>. I filled it in onbindviewholder. as I described it before. All header items positions are not calculated at once until I scroll all the list to end .so returnPositionHedaer function always return 0.

RecyclerViewAdapter Class:

class RecyclerAdapterInside( PlaceId:String?=null, var myReceivedData: List<ItemClass>?=null, val context:Context, private val ButtonFinish:RecyclerAdapterInsideButtonFinishCallback):RecyclerView.Adapter<RecyclerView.ViewHolder>() {

private var allHeader:HashMap<String,Int> = hashMapOf()

    companion object {
        const val Type_Header = 1
        const val Type_Item = 2
    }
  private inner class CategoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var message: TextView = itemView.findViewById(R.id.textViewCategory)
        fun bind(position: Int)
        {
            Log.d("inner Category","it is in CategoryViewHolder Header")
            message.text = myReceivedData?.get(position)?.name

            allHeader.put(message.text.toString(),position)

            Log.d("itemss iewHolder",allHeader.keys.toString())

        }
    }
    private inner class ItemViewholder(itemview: View) : RecyclerView.ViewHolder(itemview) {
        val Name = itemview.findViewById(R.id.ItemName) as TextView
        val Price = itemview.findViewById(R.id.ItemPrice) as TextView
        val Category = itemview.findViewById(R.id.ItemCategory) as TextView
        val Image = itemView.findViewById(R.id.ImgInsideMenu) as ImageView
        val BtnAdd = itemView.findViewById(R.id.AddBtn) as Button
        var count = itemview.findViewById(R.id.showCounter) as TextView
        val BtnMin = itemView.findViewById(R.id.MinBtn) as Button
        fun bind(position: Int) {

            Log.d("value name is","${Name.text}")

            Log.d("inner Category","it is in ItemViewHolder Header")

            Name.text = myReceivedData!![position].name
        Price.text = "Price :" + myReceivedData!![position].price
...
}

    override fun getItemViewType(position: Int): Int {
        return when (myReceivedData!!.get(position).getviewType()) {
            1 -> Type_Header
            2 -> Type_Item
            else -> -1
        }

    }

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        if (viewType == Type_Header)
        {
            Log.d("OncreateViewHolder","it is in oncreateview holder type header")
            return CategoryViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.categoryheader, parent, false))
        }
        else
        {
            Log.d("OncreateViewHolder","it is in oncreateview holder type item")
            return ItemViewholder(LayoutInflater.from(parent.context).inflate(R.layout.itemforrecyclerviewinside, parent, false)
            )
        }
    }

   override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
    {
        when (myReceivedData!![position].getviewType())
        {
            Type_Header ->
            {
                Log.d("OnBindViewHolder","it is in OnBind holder type header")
                (holder as CategoryViewHolder).bind(position)
            }
            Type_Item ->
            {
                Log.d("OnTypeViewHolder","it is in OnBind holder type item")
                (holder as ItemViewholder).bind(position)
            }
        }
    }
   


 fun returnPositionHedaer(name:String):Int
        {
            for (hashMap in allHeader)
            {
                Log.d("itemss",hashMap.key)
                if (hashMap.key == name)
                {
                 return hashMap.value
                }
            }
            return 0

        }
}

I fill my TabLayout tab dynamically with Category food List. my adapter uses same category for header items.

those parts are worked. I write them here to more clarify .In this part I call returnPositionHedaer and pass the tabName of selected tab to it. this method seems never worked . I repeat that I have a dynamic position in recyclerview and I need calculate positions of header items to scroll when related tab is clicked in TabLayout. my relation or link between tab in tabLayout and item in recyclerview is Name. my method is not worked.

what approach do you suggest ?

MenuFragment Class:

//to more clarify,has no problem 
private fun setupTabLayout():List<String>
{
    if(tabLayout!!.tabCount>0)
        CoroutineScope(Dispatchers.Main).launch{tabLayout!!.removeAllTabs()}
for(i in 0 until fullMenu!!.size){
    CategoryList.add(i,fullMenu!![i].category)
    Log.d("category",CategoryList[i])
}

val tabCategory=CategoryList.distinct()



CoroutineScope(Dispatchers.Main).launch {
    if (tabCategory.size > 3)
        tabLayout!!.setTabMode(TabLayout.MODE_SCROLLABLE);
    else {
        tabLayout!!.setTabMode(TabLayout.MODE_FIXED);
        tabLayout!!.setTabGravity(TabLayout.GRAVITY_FILL);
    }

    for (i in 0 until tabCategory.size)
    {
        tabLayout?.addTab(tabLayout!!.newTab().setText(tabCategory[i]), i)
        Log.d("addingtab",tabCategory[i])
    }
} }  //to more clarify,has no problem 





 override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

 setupTabLayout()

  tabLayout!!.addOnTabSelectedListener(object: TabLayout.OnTabSelectedListener{


            override fun onTabReselected(tab: TabLayout.Tab?) {

            }

            override fun onTabUnselected(tab: TabLayout.Tab?) {

            }

            override fun onTabSelected(tab: TabLayout.Tab?) {
                isUserScrolling = false
                val position = tab!!.position
                val tabName = tab.text

                CoroutineScope(Dispatchers.Main).launch {

recyclerview.smoothScrollToPosition( recyclerAdapter.returnPositionHedaer(tabName.toString()))
                //always return 0 position
}
            }
        })
}

Solution

  • For those who have this challenge , I could handle it easily . You just need create an array from your recyclerview adapter data model. before passing to adapter create a property, for example named it poistion. the purpose is for saving position of each item in your array data model and use counter to assign position to each array item. finally pass it to adapter. Now you know what item has what position.

    MenuClass()

    open class ItemClass()
    {
        private var position = 0
    
        var name: String?=null
        var _id: String? =null
        var __v: Int? =0
        var category: String?=null
        var description: String?=null
        var image: String? =null
        var price: String? =null
        var rating: String? =null
    
        open fun ItemHeaderClass(position:Int,viewType: Int, name: String) {
                this.position=position
                this.viewType = viewType
                this.name=name
            }
    
    }
    

    MenuFragment

    for(item in category)
            {
                val myclass=ItemClass()
                myclass.ItemHeaderClass(counter++, 1, item)
                calculatedAllItemOfList.add(myclass)
    
                val items = createItemListForRecycler(item, allItem)
                calculatedAllItemOfList.addAll(items)
            }