Mehedi Hassan Piash | Senior Software Engineer | Android | iOS | KMP | Ktor | Jetpack Compose | React-Native.

September 14, 2021

RecyclerView with Multiple viewTypes and layouts

September 14, 2021 Posted by Piash , No comments

There is a common feature of e-commerce app product page is multiple view of recyclerView with different layout. This feature is little bit tricky in terms of recyclerView. Today we will implement recyclerView with multiple viewTypes and layouts. Let’s start step by step…

  1. adapter_parent_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools">

<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/child_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:itemCount="5"
tools:listitem="@layout/adapter_child_vertical_layout" />
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>

2. ParentAdapter

package com.piashcse.experiment.mvvm_hilt.ui.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.piashcse.experiment.mvvm_hilt.constants.AppConstants
import com.piashcse.experiment.mvvm_hilt.databinding.AdapterParentLayoutBinding
import com.piashcse.experiment.mvvm_hilt.model.ChildData
import com.piashcse.experiment.mvvm_hilt.model.ParentData
import com.piashcse.experiment.mvvm_hilt.utils.dpToPx

class ParentAdapter(val context: Context) : RecyclerView.Adapter<ParentAdapter.ParentViewHolder>() {
private var numberOfTab: MutableList<ParentData> = arrayListOf()
private lateinit var childAdapter: ChildAdapter
private var customViewType: Int = AppConstants.ViewType.LIST_VIEW_TYPE
fun addItems(
newItems: MutableList<ParentData>?,
clearPreviousItem: Boolean = false
) {
newItems?.let {
if (clearPreviousItem) {
numberOfTab.clear()
numberOfTab.addAll(newItems)
} else {
numberOfTab.addAll(newItems)
}

notifyDataSetChanged()
}
}

fun changeViewType(viewType: Int) {
customViewType = viewType
notifyDataSetChanged()
}

fun getCustomViewType(): Int {
return childAdapter.getCustomViewType()
}

inner class ParentViewHolder(val binding: AdapterParentLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(childItems: ArrayList<ChildData>) {
childAdapter = ChildAdapter()
binding.childRecycler.apply {
if (customViewType == AppConstants.ViewType.LIST_VIEW_TYPE) {
layoutManager = GridLayoutManager(context, 1)
setPadding(10.dpToPx(), 0, 10.dpToPx(), 0)
} else {
layoutManager = GridLayoutManager(context, 2)
setPadding(5.dpToPx(), 0, 5.dpToPx(), 0)
}
childAdapter.changeViewType(customViewType)
adapter = childAdapter
}
childAdapter.addItems(childItems)
}
}


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParentViewHolder {
val bind =
AdapterParentLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ParentViewHolder(bind)
}

override fun onBindViewHolder(holder: ParentViewHolder, position: Int) {
holder.bind(numberOfTab[position].childItems)
}

override fun getItemCount() = numberOfTab.size
}

3. adapter_child_vertical_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>

<data>

<variable
name="item"
type="com.piashcse.experiment.mvvm_hilt.model.ChildData" />
</data>

<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:layout_marginBottom="10dp"
android:orientation="vertical">

<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:orientation="vertical">

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{item.title}"
tools:text="@string/title" />

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{Double.toString(item.price)}"
tools:text="@string/price" />

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="@string/is_stock" />
</androidx.appcompat.widget.LinearLayoutCompat>

<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="150dp"
android:scaleType="centerCrop"
app:srcCompat="@drawable/image_nature" />

</androidx.appcompat.widget.LinearLayoutCompat>
</layout>

4. adapter_child_horizontal_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>

<data>

<variable
name="item"
type="com.piashcse.experiment.mvvm_hilt.model.ChildData" />
</data>

<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:orientation="horizontal"
android:weightSum="2">

<androidx.appcompat.widget.AppCompatImageView
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="1"
android:scaleType="centerCrop"
app:srcCompat="@drawable/image_nature" />

<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:layout_marginStart="15dp"
android:layout_weight="1"
android:orientation="vertical">

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{item.title}"
tools:text="@string/title" />

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{Double.toString(item.price)}"
tools:text="@string/price" />

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="@string/is_stock" />
</androidx.appcompat.widget.LinearLayoutCompat>


</androidx.appcompat.widget.LinearLayoutCompat>
</layout>

5. ChildAdapter

package com.piashcse.experiment.mvvm_hilt.ui.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.piashcse.experiment.mvvm_hilt.constants.AppConstants
import com.piashcse.experiment.mvvm_hilt.databinding.AdapterChildHorizontalLayoutBinding
import com.piashcse.experiment.mvvm_hilt.databinding.AdapterChildVerticalLayoutBinding
import com.piashcse.experiment.mvvm_hilt.model.ChildData

class ChildAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var items: MutableList<ChildData> = arrayListOf()
private var customVIewType: Int = AppConstants.ViewType.LIST_VIEW_TYPE

fun addItems(newItems: MutableList<ChildData>?, clearPreviousItem: Boolean = false) {
newItems?.let {
if (clearPreviousItem) {
items.clear()
items.addAll(newItems)
} else {
items.addAll(newItems)
}
notifyDataSetChanged()
}
}

fun changeViewType(viewType: Int) {
customVIewType = viewType
}

fun getCustomViewType(): Int {
return customVIewType
}

inner class VerticalViewHolder(val binding: AdapterChildVerticalLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(childItem: ChildData) {
binding.apply {
item = childItem
executePendingBindings()
}
}
}

inner class HorizontalViewHolder(val binding: AdapterChildHorizontalLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(childItem: ChildData) {
binding.apply {
item = childItem
executePendingBindings()
}
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (customVIewType == AppConstants.ViewType.LIST_VIEW_TYPE) {
val binding = AdapterChildHorizontalLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
HorizontalViewHolder(binding)
} else {
val binding = AdapterChildVerticalLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
VerticalViewHolder(binding)
}
}


override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is VerticalViewHolder) {
holder.bind(items[position])
} else if (holder is HorizontalViewHolder) {
holder.bind(items[position])
}
}

override fun getItemCount() = items.size

}

6. FragmentViewPagerWithNestedRecyclerView

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/background_default"
tools:context=".ui.fragment.ViewPagerWithNestedRecyclerViewFragment">

<androidx.appcompat.widget.AppCompatButton
android:id="@+id/view_change"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/view_change"/>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.appcompat.widget.LinearLayoutCompat>

7. ViewPagerWithNestedRecyclerViewFragment

package com.piashcse.experiment.mvvm_hilt.ui.fragment

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.tabs.TabLayoutMediator
import com.piashcse.experiment.mvvm_hilt.databinding.FragmentViewPagerWithNestedRecyclerViewBinding
import com.piashcse.experiment.mvvm_hilt.ui.adapter.ParentAdapter
import dagger.hilt.android.AndroidEntryPoint
import android.widget.TextView
import com.piashcse.experiment.mvvm_hilt.constants.AppConstants
import com.piashcse.experiment.mvvm_hilt.model.ChildData
import com.piashcse.experiment.mvvm_hilt.model.ParentData


@AndroidEntryPoint
class ViewPagerWithNestedRecyclerViewFragment : Fragment() {
private var _binding: FragmentViewPagerWithNestedRecyclerViewBinding? = null
private val binding get() = requireNotNull(_binding) // or _binding!!
private val parentAdapter: ParentAdapter by lazy {
ParentAdapter(requireContext())
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentViewPagerWithNestedRecyclerViewBinding.inflate(
layoutInflater,
container,
false
)

return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}

private fun initView() {
binding.apply {

viewPager.apply {
adapter = parentAdapter
parentAdapter.addItems(generateData())
}
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = generateData()[position].title
}.attach()
setAllCaps(tabLayout, false)

viewChange.setOnClickListener {
if (parentAdapter.getCustomViewType() == AppConstants.ViewType.LIST_VIEW_TYPE) {
parentAdapter.changeViewType(AppConstants.ViewType.GRID_VIEW_TYPE)
} else {
parentAdapter.changeViewType(AppConstants.ViewType.LIST_VIEW_TYPE)
}
}
}
}

private fun setAllCaps(view: View?, caps: Boolean) {
if (view is ViewGroup) {
for (i in 0 until view.childCount) setAllCaps(view.getChildAt(i), caps)
} else if (view is TextView) view.isAllCaps = caps
}

private fun generateData(): MutableList<ParentData> {
return mutableListOf(
ParentData(
"Tab One", arrayListOf(
ChildData("Shirt-Red", 200.0, true, ""),
ChildData("Shirt-Blue", 300.0, true, "")
)
),
ParentData(
"Tab Tow", arrayListOf(
ChildData("Pant-Formal", 300.0, true, ""),
ChildData("Pant-Jeans", 400.0, true, "")
)
),
ParentData(
"Tab Two", arrayListOf(
ChildData("T-Shirt-Grey", 150.0, true, ""),
ChildData("T-Shirt-Black", 200.0, true, "")
)
),
ParentData(
"Tab Three", arrayListOf(
ChildData("T-Shirt-Grey", 150.0, true, ""),
ChildData("T-Shirt-Black", 200.0, true, "")
)
)
)
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

Ref: https://github.com/piashcse/blog_piashcse_code

0 comments:

Post a Comment