As you know, you can create RecyclerView with multiple ViewHolders follow step:
getItemViewType()
onCreateViewHolder()
onBindViewHolder()
But i can only create two viewholder in other row, example:
I have a list (item 1 - type 1, item 2 - type 1, item 3 - type 2)
So when i use GridLayoutManager with span column is 2, item-1 and item-2 is same row because it same type,
**I have another list (item 1 - type 1, item 2 - type 3, item 3 - type 2)
**
They will be arranged 3 three rows, so i want combine item1 and item 2 same row, not combine layout. Can anyone have an idea?
package info.androidhive.recyclerview
import android.annotation.TargetApi
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
class MoviesAdapter(private val moviesList: List<Movie>) : RecyclerView.Adapter<MoviesAdapter.ViewHolder>() {
var pos = true
fun notifyLayoutChanged(context: Context) {
val isLanscape = context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
convert(moviesList, isLanscape)
notifyDataSetChanged()
}
#TargetApi(Build.VERSION_CODES.N)
private fun convert(moviesList: List<Movie>, isLanscape: Boolean) {
if (isLanscape) {
moviesList.forEach {
if (it.type == Movie.Type.PICKUP) {
it.type = Movie.Type.BANNER
}
}
}
}
class MyViewHolder(view: View) : ViewHolder(view) {
var title: TextView
var year: TextView? = null
var genre: TextView
init {
title = view.findViewById<View>(R.id.title) as TextView
genre = view.findViewById<View>(R.id.genre) as TextView
}
override fun bind(movie: Movie) {
title.text = movie.title
genre.text = movie.genre
}
companion object {
fun create(context: Context): MyViewHolder {
return MyViewHolder(LayoutInflater.from(context)
.inflate(R.layout.movie_list_row, null, false))
}
}
}
class BannerViewHolder(view: View) : ViewHolder(view) {
var view1: MovieView
var view2: MovieView
init {
view1 = view.findViewById<View>(R.id.item1) as MovieView
view2 = view.findViewById<View>(R.id.item2) as MovieView
}
override fun bind(movie: Movie) {
view1.bind(movie)
view2.bind(movie)
}
companion object {
fun create(context: Context): BannerViewHolder {
return BannerViewHolder(LayoutInflater.from(context)
.inflate(R.layout.banner, null, false))
}
}
}
abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(movie: Movie)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
if (viewType == Movie.Type.BANNER.ordinal) {
if (pos) {
pos = false
return MyViewHolder.create(parent.context)
} else {
pos = true
return BannerViewHolder.create(parent.context)
}
} else {
return MyViewHolder.create(parent.context)
}
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView?) {
super.onDetachedFromRecyclerView(recyclerView)
}
override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
if (holder != null) {
val movie = moviesList[position]
holder.bind(movie)
}
}
fun getItem(position: Int): Movie {
return moviesList[position]
}
override fun getItemViewType(position: Int): Int {
val movie = moviesList[position]
return movie.type.ordinal
}
override fun getItemCount(): Int {
return moviesList.size
}
}
Related
I forgot how to check a single checkbox in a recyclerview. I looked at how to have single choice on checkbox in recyclerview? but you shouldn't need to call notifyDataSetChanged. I tried this and the wrong checkbox is being selected:
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.tps.challenge.R
import com.tps.challenge.domain.Store
import com.tps.challenge.ui.Event
class StoreFeedAdapter(
private val stores: List<Store>,
private val itemClickListener: (Event<Store>) -> Unit
): RecyclerView.Adapter<StoreItemViewHolder>() {
private var prevSelectedPos: Int = 0
private var currSelectedPos: Int = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StoreItemViewHolder {
return StoreItemViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_store, parent, false)
).also { viewHolder ->
viewHolder.storeImage.setOnClickListener {
val selectedStore = stores[viewHolder.adapterPosition]
itemClickListener.invoke(
Event(selectedStore)
)
}
viewHolder.checkbox.setOnClickListener {
currSelectedPos = viewHolder.adapterPosition
notifyItemChanged(prevSelectedPos)
notifyItemChanged(currSelectedPos)
prevSelectedPos = currSelectedPos
}
}
}
override fun onBindViewHolder(holder: StoreItemViewHolder, position: Int) {
holder.checkbox.isSelected = currSelectedPos == position
val store = stores[position]
with(holder.itemView) {
findViewById<TextView>(R.id.name).text = store.name
findViewById<TextView>(R.id.description).text = store.desc
Glide.with(holder.itemView.context)
.load(store.coverImgUrl)
.into(holder.storeImage)
}
}
override fun getItemCount(): Int {
return stores.size
}
}
class StoreItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val storeImage: ImageView = itemView.findViewById(R.id.iv_store)
val checkbox: CheckBox = itemView.findViewById(R.id.cb_select)
}
class StoreFeedAdapter(
private val stores: List<Store>,
private val itemClickListener: (Event<Store>) -> Unit
): RecyclerView.Adapter<StoreItemViewHolder>() {
private var prevSelectedPos: Int = 0
private var currSelectedPos: Int = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StoreItemViewHolder {
return StoreItemViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_store, parent, false)
).also { viewHolder ->
viewHolder.storeImage.setOnClickListener {
val selectedStore = stores[viewHolder.adapterPosition]
itemClickListener.invoke(
Event(selectedStore)
)
}
viewHolder.checkbox.setOnClickListener {
currSelectedPos = viewHolder.adapterPosition
notifyDataSetChanged()
}
}
}
override fun onBindViewHolder(holder: StoreItemViewHolder, position: Int) {
if(currSelectedPos == position){checked}else{unChecked}
val store = stores[position]
with(holder.itemView) {
findViewById<TextView>(R.id.name).text = store.name
findViewById<TextView>(R.id.description).text = store.desc
Glide.with(holder.itemView.context)
.load(store.coverImgUrl)
.into(holder.storeImage)
}
}
override fun getItemCount(): Int {
return stores.size
}
}
class StoreItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val storeImage: ImageView = itemView.findViewById(R.id.iv_store)
val checkbox: CheckBox = itemView.findViewById(R.id.cb_select)
}
Recently, I was faced with an issue that is a bit hard for me to solve
I need to put 2 models in the List adapter, but it always says 2 types of arguments expected
Here is the Link of the whole project
and I want to have Transaction and Bank models in ListAdapter
you can read the project README.
The adapter that I want to have 2 models is in ui.TransactionHistory
here is my whole adapter class:
class TransactionHistory() :
ListAdapter<Transaction, RecyclerView.ViewHolder>(BillDiffCallback()) {
private val ITEM_VIEW_TYPE_EMPTY = 0
private val ITEM_VIEW_TYPE_ITEM = 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIEW_TYPE_ITEM -> ViewHolder.from(parent)
ITEM_VIEW_TYPE_EMPTY -> EmptyViewHolder.from(parent)
else -> throw ClassCastException("Unknown viewType $viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
val item = getItem(position)
holder.bind(item, clickListener)
// holder.bind2(Bank, clickListener)
}
is EmptyViewHolder -> {
holder.bind()
}
}
}
lateinit var clickListener: AdapterListener2
fun setOnclickListener(listener: AdapterListener2) {
clickListener = listener
}
override fun getItemViewType(position: Int): Int {
return if (itemCount > 0)
ITEM_VIEW_TYPE_ITEM
else
ITEM_VIEW_TYPE_EMPTY
}
class ViewHolder
private constructor(val binding: ItemUserTransactionListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Transaction, adapterListener2: AdapterListener2) {
binding.transaction = item
binding.clickListener = adapterListener2
binding.executePendingBindings()
if (item.type == "payPayment") {
binding.transactionStatus.text = "برداخت قسط"
} else if (item.type == "decrease") {
binding.transactionStatus.text = "برداشت"
} else if (item.type == "increase") {
binding.transactionStatus.text = "واریز"
}
if (item.decrease == null) {
binding.amount.text = item.increase
} else {
binding.amount.text = item.decrease
}
}
fun bind2(item2: Bank, adapterListener2: AdapterListener2) {
binding.bankInfo = item2
binding.clickListener = adapterListener2
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserTransactionListBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class EmptyViewHolder private constructor(val binding: ItemUserTransactionListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind() {
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): EmptyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserTransactionListBinding.inflate(layoutInflater, parent, false)
return EmptyViewHolder(binding)
}
}
}
class BillDiffCallback : DiffUtil.ItemCallback<Transaction>() {
override fun areItemsTheSame(oldItem: Transaction, newItem: Transaction): Boolean {
return oldItem.transId == newItem.transId
}
override fun areContentsTheSame(
oldItem: Transaction,
newItem: Transaction
): Boolean {
return oldItem == newItem
}
}
class AdapterListener2(
val clickListener: (id: Long?) -> Unit,
val deleteListener: (category: Transaction) -> Unit
) {
fun onclick(transaction: Transaction) = clickListener(transaction.userId)
fun onDeleteClick(userInfo: Transaction) = deleteListener(userInfo)
}
whenever i placed secound model here:
ListAdapter<Transaction, **Bank**, RecyclerView.ViewHolder>(BillDiffCallback()) {}
it says 2 type argument expected.
I don't know if it helps you or not but some one told me I have to use join in Kotlin
THANKS FOR YOUR ANSWERS :)
ListAdaper can only accept one data model however you can add multiple items using another class like Sealed Class
sealed class DataItem {
abstract val id: Long
data class TransactionItem(val transaction: Transaction): DataItem() {
override val id = transaction.transId
}
object Empty: DataItem() {
override val id = Long.MIN_VALUE
}
}
and deal with this only class in your ListAdapter for your code you will need to apply this changes
class TransactionHistory() :
ListAdapter<DataItem, RecyclerView.ViewHolder>(BillDiffCallback()) {
private val ITEM_VIEW_TYPE_EMPTY = 0
private val ITEM_VIEW_TYPE_ITEM_TRANSACTION = 1
private val ITEM_VIEW_TYPE_ITEM_BANK = 2
private val adapterScope = CoroutineScope(Dispatchers.Default)
/**
* DO NOT USE .submit(), use the method bellow
*/
fun addTransactionsAndBanks(transactionList: List<Transaction>?, bankList: List<Bank>?) {
adapterScope.launch {
val transactionItems: List<DataItem> = when {
transactionList == null || transactionList.isEmpty() -> listOf(DataItem.Empty)
else -> transactionList.map { DataItem.TransactionItem(it) }
}
val bankItems: List<DataItem> = when {
bankList == null || bankList.isEmpty() -> listOf(DataItem.Empty)
else -> bankList.map { DataItem.BankItem(it) }
}
val items = transactionItems + bankItems
withContext(Dispatchers.Main) {
submitList(items)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIEW_TYPE_ITEM_TRANSACTION -> ViewHolder.from(parent)
ITEM_VIEW_TYPE_ITEM_BANK -> ViewHolder.from(parent)
ITEM_VIEW_TYPE_EMPTY -> EmptyViewHolder.from(parent)
else -> throw ClassCastException("Unknown viewType $viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
when (val item = getItem(position)) {
is DataItem.TransactionItem -> holder.bind(item.transaction, clickListener)
is DataItem.BankItem -> holder.bind2(item.bank, clickListener)
}
}
is EmptyViewHolder -> holder.bind()
}
}
lateinit var clickListener: AdapterListener2
fun setOnclickListener(listener: AdapterListener2) {
clickListener = listener
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is DataItem.Empty -> ITEM_VIEW_TYPE_EMPTY
is DataItem.TransactionItem -> ITEM_VIEW_TYPE_ITEM_TRANSACTION
is DataItem.BankItem -> ITEM_VIEW_TYPE_ITEM_BANK
}
}
class ViewHolder
private constructor(val binding: ItemUserTransactionListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Transaction, adapterListener2: AdapterListener2) {
binding.transaction = item
binding.clickListener = adapterListener2
binding.executePendingBindings()
if (item.type == "payPayment") {
binding.transactionStatus.text = "برداخت قسط"
} else if (item.type == "decrease") {
binding.transactionStatus.text = "برداشت"
} else if (item.type == "increase") {
binding.transactionStatus.text = "واریز"
}
if (item.decrease == null) {
binding.amount.text = item.increase
} else {
binding.amount.text = item.decrease
}
}
fun bind2(item2: Bank, adapterListener2: AdapterListener2) {
binding.bankInfo = item2
binding.clickListener = adapterListener2
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserTransactionListBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class EmptyViewHolder private constructor(val binding: ItemUserTransactionListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind() {
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): EmptyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemUserTransactionListBinding.inflate(layoutInflater, parent, false)
return EmptyViewHolder(binding)
}
}
}
class BillDiffCallback : DiffUtil.ItemCallback<DataItem>() {
override fun areItemsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem == newItem
}
}
class AdapterListener2(
val clickListener: (id: Long?) -> Unit,
val deleteListener: (category: Transaction) -> Unit
) {
fun onclick(transaction: Transaction) = clickListener(transaction.userId)
fun onDeleteClick(userInfo: Transaction) = deleteListener(userInfo)
}
sealed class DataItem {
abstract val id: Long
data class TransactionItem(val transaction: Transaction) : DataItem() {
override val id = transaction.transId
}
data class BankItem(val bank: Bank) : DataItem() {
override val id = bank.bankId
}
object Empty : DataItem() {
override val id = Long.MIN_VALUE
}
}
You can build the list based on only a single data source. If you want to have multiple data sources, you should create a third data model and add the other two models in it.
data class ListData(val transaction : Transaction , val Bank : Bank)
ListView/Recycler view will create number of list items based on the list of objects passed.
ListAdapter<ListData, RecyclerView.ViewHolder>
I found the answer to my Question
We have to use Join in query:
//for single info
#Query("SELECT `transaction`.increase, `transaction`.decrease, bank.bank_name From `transaction` JOIN bank WHERE `transaction`.bank_id=:key ")
fun joinTables(key: Long): LiveData<TransactionAndBank>?
//for list of info
#Query("SELECT `transaction`.increase, `transaction`.decrease,`transaction`.type, bank.bank_name From `transaction` JOIN bank WHERE `transaction`.bank_id=:key ")
fun joinAllTables(key: Long): LiveData<List<TransactionAndBank>>
I had to place it In TransactionDAO
source
There are two Recyclerview in same activity rv1 and rv2. Selected item of rv1 will show in rv2
rv1:
Here I select multiple items
rv2:
it should shows like this
Adapter class of rv1:
class ServiceJobAdapter(val context: Context, var list:ArrayList<JobRespo>,var listener:OnItemClick):
RecyclerView.Adapter<ServiceJobAdapter.ItemsCarrier>(){
var currentSelectedIndex= -1
var selectedItem = SparseBooleanArray()
var animationItemsIndex = SparseBooleanArray()
private val reverseAllAnimations = false
var holder:ItemsCarrier? =null
class ItemsCarrier(itemView: View) : RecyclerView.ViewHolder(itemView)
{
var jobName = itemView.findViewById<TextView>(R.id.job_name)
var jobPrice = itemView.findViewById<TextView>(R.id.job_price)
var iconBack = itemView.findViewById(R.id.icon_back) as RelativeLayout
var iconFront = itemView.findViewById(R.id.icon_front) as RelativeLayout
var iconContainer = itemView.findViewById(R.id.icon_container) as CardView
fun binde(jobRespo: JobRespo) {
jobName.text = jobRespo.repDesc
jobPrice.text="₹"+jobRespo.repRice
if (iconFront.visibility == View.VISIBLE)
{
jobRespo.setChecked(false)
}else{
jobRespo.setChecked(true)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemsCarrier {
val view = LayoutInflater.from(context).inflate(R.layout.job_row,parent,false)
return ItemsCarrier(view)
}
override fun getItemCount(): Int {
return list.size
}
override fun onBindViewHolder(holder: ItemsCarrier, position: Int) {
this.holder = holder
holder.itemView.isActivated = selectedItem[position,false]
holder.binde(list[position])
applyIconAnimation(holder, position)
// apply click events
applyClickEvents(holder, position)
}
private fun applyClickEvents(holder: ItemsCarrier, position: Int) {
holder.iconContainer.setOnClickListener {
listener.onIconClicked(position)
listener.AllItem(list[position]) }
}
private fun applyIconAnimation(holder: ItemsCarrier, position: Int)
{
if (selectedItem.get(position, false)) {
holder.iconFront!!.visibility = View.GONE
resetIconYAxis(holder.iconBack)
holder.iconBack.visibility = View.VISIBLE
holder.iconBack.alpha = 1f
if (currentSelectedIndex == position) {
FlipAnimator.flipView(context, holder.iconBack, holder.iconFront, true)
resetCurrentIndex()
}
} else {
holder.iconBack.visibility = View.GONE
resetIconYAxis(holder.iconFront)
holder.iconFront.visibility = View.VISIBLE
holder.iconFront.alpha = 1f
if (reverseAllAnimations && animationItemsIndex.get(
position,
false
) || currentSelectedIndex == position
) {
FlipAnimator.flipView(context, holder.iconBack, holder.iconFront, false)
resetCurrentIndex() } } }
private fun resetIconYAxis(view: RelativeLayout?)
{
if (view!!.rotationY != 0f) {
view.rotationY = 0f
}
}
private fun resetCurrentIndex() {
currentSelectedIndex = -1
}
fun toggleSelection(pos: Int) {
currentSelectedIndex = pos
if (selectedItem.get(pos, false)) {
selectedItem.delete(pos)
animationItemsIndex.delete(pos)
} else {
selectedItem.put(pos, true)
animationItemsIndex.put(pos, true)
}
notifyItemChanged(pos)
}
fun getSelectedItems(): ArrayList<JobRespo>
{
val selectItems = ArrayList<JobRespo>()
for(item in list)
{
if (!item.isChecked())
{
selectItems.add(item)
}
}
return selectItems}}
Using Interface
interface OnItemClick{
fun onIconClicked(position: Int)
fun AllItem(jobs:JobRespo)}
Implement interface in Activity
override fun onIconClicked(position: Int) {
mAdapter!!.toggleSelection(position)
}
override fun AllItem(jobs: JobRespo) {
val selectedItems:ArrayList<JobRespo>
=mAdapter!!.getSelectedItems()
selectIte.addAll(selectedItems)
}
Adapter class of 2nd Recyclerview:
lass SelectItemAdapter(val context: Context, var list:List<JobRespo>): RecyclerView.Adapter<SelectItemAdapter.ItemsCarriers>(){
class ItemsCarriers(itemView: View) : RecyclerView.ViewHolder(itemView) {
var itemName = itemView.findViewById<TextView>(R.id.item_name)
var itemPrice= itemView.findViewById<TextView>(R.id.item_peice)
var itmDelet =itemView.findViewById<LinearLayout>(R.id.item_delete)
fun bind(job: JobRespo) {
itemName.text = job.repDesc
itemPrice.text = job.repRice }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemsCarriers {
val root = LayoutInflater.from(context).inflate(R.layout.select_item_row,parent,false)
return ItemsCarriers(root)
}
override fun getItemCount(): Int {
return list.size
}
override fun onBindViewHolder(holder: ItemsCarriers, position: Int) {
holder.bind(list[position])}}
Now how can i pass multiselected data from one recyclerview to anathor recyclerview of same activity?
Thanks your consideration and guidded to me :)
You can create a method in a second adapter to update the selected value like below and use it when you getting selected items.
fun setSelectedData(data:ArrayList<JobRespo>){
this.list.clear()
this.list.addAll(data)
notifyDataSetChanged ();
}
What's the correct way to implement AdMob ads (as a different view type) inside a Kotlin RecyclerView that contains an additional (but different) view type? I've not seen any helpful tutorials online. The issue I'm having is with the onCreateViewHolder method as I'm unsure of the correct way to modify it.
class AdapterMain(
private val mCtx: Context,
var myList: MutableList<ItemRV>
) : androidx.recyclerview.widget.RecyclerView.Adapter<AdapterMain
.MyViewHolder>() {
private val myListFull = myList.toMutableList()
private var mClickListener: ItemClickListener? = null
private val itemRV = 1
private val itemAD = 2
override fun getItemViewType(position: Int): Int {
return if (position % 5 == 0) {
itemRV
} else {
itemAD
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) {
return if (viewType == itemAD) {
val adLoader = AdLoader.Builder(mCtx, "ca-app-pub-5544923106349792~1702536043")
.forUnifiedNativeAd { ad : UnifiedNativeAd ->
// Show the ad.
}
.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(errorCode: Int) {
// Handle the failure by logging, altering the UI, and so on.
}
})
.withNativeAdOptions(
NativeAdOptions.Builder()
.build())
.build()
adLoader.loadAd(AdRequest.Builder().build())
} else {
val inflater = LayoutInflater.from(mCtx)
val v = inflater.inflate(R.layout.rv_item, parent, false)
return MyViewHolder(v)
}
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
if (position == itemAD){
} else {
val product = myList[holder.adapterPosition]
holder.tvTitle.text = product.itemName
}
}
override fun getItemCount(): Int {
return myList.size
}
inner class MyViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView
.ViewHolder(itemView), View.OnClickListener {
var tvTitle: TextView = itemView.tv_title
}
interface ItemClickListener {
fun onItemClick(view: View, position: Int)
}
}
What you want to do is create 2 classes that extend ViewHolder; one for your normal content, and one for your ad view. You'll need to change some of your functions to use the generic RecyclerView.ViewHolder instead of your MyViewHolder as it could be either one.
In your onCreateViewHolder, you'll want to create one or the other, and return that.
And then in your onBindViewHolder, that's where you'll want to load/show your ad.
class AdapterMain(
private val mCtx: Context,
var myList: MutableList<ItemRV>
) : androidx.recyclerview.widget.RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val myListFull = myList.toMutableList()
private var mClickListener: ItemClickListener? = null
private val itemRV = 1
private val itemAD = 2
override fun getItemViewType(position: Int): Int {
return if (position % 5 == 0) {
itemRV
} else {
itemAD
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) {
// Create an ad view holder
return if (viewType == itemAD) {
val v = LayoutInflater.from(mCtx).inflate(R.layout.ad_item, parent, false)
AdViewHolder(v)
} else {
val v = LayoutInflater.from(mCtx).inflate(R.layout.rv_item, parent, false)
MyViewHolder(v)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder) {
// This is where you want to load/show the ad.
is AdViewHolder -> {
val adLoader = AdLoader.Builder(mCtx, "ca-app-pub-5544923106349792~1702536043")
.forUnifiedNativeAd { ad : UnifiedNativeAd ->
// Show the ad.
}
.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(errorCode: Int) {
// Handle the failure by logging, altering the UI, and so on.
}
})
.withNativeAdOptions(
NativeAdOptions.Builder()
.build())
.build()
adLoader.loadAd(AdRequest.Builder().build())
}
is MyViewHolder -> {
val product = myList[holder.adapterPosition]
holder.tvTitle.text = product.itemName
}
}
}
override fun getItemCount(): Int {
return myList.size
}
inner class MyViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView
.ViewHolder(itemView), View.OnClickListener {
var tvTitle: TextView = itemView.tv_title
}
inner class AdViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView
.ViewHolder(itemView), View.OnClickListener {
// do any ad setup
}
interface ItemClickListener {
fun onItemClick(view: View, position: Int)
}
}
I have a RecyclerView and Every odd row has a different background color.
I try to add DiffUtil to speed up the updates like this:
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.asdf.android.R
import com.asdf.android.network.model.trade.MarketTradeItem
import kotlinx.android.synthetic.main.row_trade_history.view.*
class MarketTradesListAdapter(
private val context: Context,
private var list: MutableList<MarketTradeItem>,
val theme: Int
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflate =
LayoutInflater.from(parent.context).inflate(R.layout.row_trade_history, parent, false)
return ItemHolder(inflate)
}
override fun getItemCount(): Int {
return list.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val itemHolder = holder as ItemHolder
itemHolder.bind(list[position], position)
}
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int,
payloads: MutableList<Any>
) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
} else {
val payload = payloads[0] as DiffUtilPayload
val itemHolder = holder as ItemHolder
itemHolder.bind(list[position], position, payload)
}
}
fun setList(it: List<MarketTradeItem>) {
it.forEachIndexed { index, item -> item.position = index }
DiffUtil.calculateDiff(DiffCallback(list, it),true).dispatchUpdatesTo(this)
list.clear()
list.addAll(it)
}
inner class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(
order: MarketTradeItem,
position: Int,
payload: DiffUtilPayload = DiffUtilPayload()
) {
var color: Int
if (theme == R.style.Apptheme) {
color = R.color.listBackgroundColorLight
} else {
color = R.color.listBackgroundColorDark
}
// if (payload.isPositionChanged)
itemView.setBackgroundColor(
if (position % 2 == 0) ContextCompat.getColor(
itemView.context,
android.R.color.transparent
) else
ContextCompat.getColor(itemView.context, color)
)
itemView.textViewPrice.setTextColor(
ContextCompat.getColor(
itemView.context,
if (order.isRaise) R.color.buy_green else R.color.sell_red
)
)
if (payload.isPriceChanged) {
itemView.textViewPrice.text = order.price
}
if (payload.isAmountChanged) {
itemView.textViewAmount.text = order.amount
}
if (payload.isTimeChanged) {
itemView.textViewTime.text = order.time
}
}
}
class DiffCallback(
private val oldList: List<MarketTradeItem>,
private val newList: List<MarketTradeItem>
) : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
override fun getOldListSize(): Int {
return oldList.size
}
override fun getNewListSize(): Int {
return newList.size
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition]
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val oldItem = oldList[oldItemPosition]
val newItem = newList[oldItemPosition]
return DiffUtilPayload(
oldItem.price != newItem.price,
oldItem.amount != newItem.amount,
oldItem.createdAt != newItem.createdAt,
oldItem.time != newItem.time,
oldItem.position != newItem.position
)
}
}
data class DiffUtilPayload(
val isPriceChanged: Boolean = true,
val isAmountChanged: Boolean = true,
val isCreatedAtChanged: Boolean = true,
val isTimeChanged: Boolean = true,
val isPositionChanged: Boolean = true
)
}
The problem is that the background color of even and odd rows does not appear correctly when new items are inserted to the list and appears like this:
The background color is set randomly and not appear as alternate even/odd.
What can I do to fix this?
I think you need to change areContentsTheSame method in your DiffUtil. If the contents are same, it won't calculate the change which won't dispatch notifyItemChanged(position) to the adapter. And it won't call onBindViewHolder for that position/item.
You should try changing the method to
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldList[oldItemPosition] == newList[newItemPosition] && oldItemPosition % 2 == newItemPosition % 2
}
if items are added at the beginning. the fast solution would be, in onBindViewHolder
itemHolder.bind(list[position], position, payload)
change to:
itemHolder.bind(list[position], getItemCount() - position, payload)
and itemHolder.bind(list[position], position)
change to:
itemHolder.bind(list[position], getItemCount() - position)
I think the view holder should not know the postion. so I suggest you some refactoring.