Home > Back-end >  How to add a button at the end of a recycler view (kotlin)?
How to add a button at the end of a recycler view (kotlin)?

Time:07-08

I'm gonna add a button at the end of my recycler view and i don't know how to do it . i will appreciate you if you teach me .

this is my main activity code :

class MainActivity : AppCompatActivity() {
private var number: Int = 0
private lateinit var name: String

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val myRecyclerView = findViewById<RecyclerView>(R.id.recyclerView)
    val myViewModel = ViewModel()
    myRecyclerView.layoutManager = LinearLayoutManager(this)

    myViewModel.getUserData()




    myViewModel.myList.observe(this) {
     
        myRecyclerView.adapter = MyAdapter(it)
    }


}

and this is my adapter code :

class MyAdapter(private val myList: List<Player>) : RecyclerView.Adapter<MyHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder {
    val myLayoutInflater = LayoutInflater.from(parent.context)
    val myView = myLayoutInflater.inflate(R.layout.view_for_rv, parent, false)
    return MyHolder(myView)

}

override fun onBindViewHolder(holder: MyHolder, position: Int) {
    val item = myList[position]
    holder.bind(item)


}

override fun getItemCount(): Int {
    return myList.size
}

and my holder class :

class MyHolder(view: View) : RecyclerView.ViewHolder(view) {
private val playerNumber  = view.findViewById<TextView>(R.id.tvNumber)
private val playerName = view.findViewById<TextView>(R.id.tvName)

fun bind(item : Player){
    playerName.text=item.name
    playerNumber.text=item.number.toString()
}

CodePudding user response:

You will achieve this by adding the RecyclerView in a parent NestedScrollView and then place the Button below the RecyclerView.

So your code should look like this

<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</androidx.core.widget.NestedScrollView>

NOTE

Add recyclerView.nestedScrollingEnabled = false for scroll to work for sdk 21

CodePudding user response:

add a new itemType is good selection。

class MyAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    companion object {
        const val ITEM_TYPE_CONTENT = 0
        const val ITEM_TYPE_BUTTON = 1
    }

    val dataList: List<String>? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        if (viewType == ITEM_TYPE_CONTENT) {
            // TODO return a content holder
        }else {
            // TODO return a button holder
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        TODO("Not yet implemented")
    }

    override fun getItemCount(): Int {
        return (dataList?.size ?: 0)   1
    }

    override fun getItemViewType(position: Int): Int {
        return if (position == itemCount - 1) {
            ITEM_TYPE_BUTTON
        } else {
            ITEM_TYPE_CONTENT
        }
    }
}

CodePudding user response:

You can use this

package com.joon.fm.core.base.genericRecycler


import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.core.util.set
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.joon.fm.core.extensions.getClass
import com.joon.fm.features.explore.section.SectionExploreNewReleases
import java.lang.reflect.Constructor
import java.util.concurrent.atomic.AtomicInteger


interface GenericAdapterView<Model> {
    fun onBind(model: Model, position: Int, extraObject: Any?)
}

data class Section<Model>(
    var type: Int,
    var data: Model,
    var extraObject: Any?,
    var index: Int = -1
)

open class VH(view: View) : RecyclerView.ViewHolder(view)

interface DiffUtilModel {
    fun areItemsTheSame(o: DiffUtilModel): Boolean
    fun areContentsTheSame(o: DiffUtilModel): Boolean
}

class MyDiffCallback(private val oldList: List<Any?>, private val newList: List<Any?>) :
    DiffUtil.Callback() {
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val old = oldList[oldItemPosition]
        val new = newList[newItemPosition]
        if (old is DiffUtilModel && new is DiffUtilModel) {
            return old.areItemsTheSame(new)
        }
        return oldList[oldItemPosition] == newList[newItemPosition]
    }

    override fun getOldListSize() = oldList.size

    override fun getNewListSize() = newList.size

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val old = oldList[oldItemPosition]
        val new = newList[newItemPosition]
        if (old is DiffUtilModel && new is DiffUtilModel) {
            return old.areContentsTheSame(new)
        }
        return (old == null && new == null) || (old != null && old == new)
    }

}

@Suppress("unused")
open class AppGenericAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    val sections = ArrayList<Section<*>>()
    var providers = SparseArray<(ViewGroup, Int) -> View>()

    private var types = HashMap<Class<*>, Pair<Int, Constructor<*>?>>()
    private var typeIds = AtomicInteger(0)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return VH(providers[viewType](parent, viewType))
    }


    @Suppress("UNCHECKED_CAST")
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val v = holder.itemView as? GenericAdapterView<Any>
        val sec = sections[position]
        sec.data?.run {
            v?.onBind(this, position, sec.extraObject)
        }
    }

    override fun getItemCount() = sections.size
    override fun getItemViewType(position: Int) = sections[position].type


    inline fun <reified V : View> viewType(addDefaultProvider: Boolean = true) =
        viewType(V::class.java, addDefaultProvider)

    fun viewType(cls: Class<*>, addDefaultProvider: Boolean = true): Int {
        val prevAns = types[cls]
        if (prevAns == null) {
            var cons: Constructor<*>? = null
            try {
                cons = cls.getDeclaredConstructor(Context::class.java)
            } catch (ignored: Throwable) {
            }
            types[cls] = Pair(typeIds.getAndIncrement(), cons)

            val type = types[cls]!!

            if (addDefaultProvider && providers[type.first] == null) {
                providers[type.first] = { vg, _ ->
                    type.second!!.newInstance(vg.context) as View
                }

            }
            return type.first
        } else {
            return prevAns.first
        }
    }


    inline fun <reified V> setProvider(noinline provider: (ViewGroup, Int) -> V) where V : View, V : GenericAdapterView<*> {
        providers[viewType<V>(false)] = provider
    }

    inline fun <reified V> provider(noinline provider: (Context) -> V) where V : View, V : GenericAdapterView<*> {
        providers[viewType<V>(false)] = { vg, _ -> provider(vg.context) }
    }

    inline fun <reified V> addProvider(noinline provider: (Context) -> V) where V : View, V : GenericAdapterView<*> {
        providers.set(viewType<V>(false)) { vg, _ -> provider(vg.context) }
    }


    inline fun <reified V, reified Model : Any> setSections(
        data: Collection<Model>,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        sections.clear()
        addSections<V, Model>(data, extra)
    }

    inline fun <reified V, reified Model : Any> setSectionsDiffUtil(
        data: Collection<Model>,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {

        val diffCallback = MyDiffCallback(sections.map { it.data }, data.toList())
        val diffResult = DiffUtil.calculateDiff(diffCallback)
        sections.clear()
        addSections<V, Model>(data, extra)
        diffResult.dispatchUpdatesTo(this)

    }

    fun setSectionsDiffUtil(newSections: Collection<Section<*>>) {
        val diffCallback = MyDiffCallback(sections.map { it.data }, newSections.map { it.data })
        val diffResult = DiffUtil.calculateDiff(diffCallback)
        sections.clear()
        sections.addAll(newSections)
        diffResult.dispatchUpdatesTo(this)
    }

    inline fun <reified V, reified Model : Any> setSectionsAndNotify(
        data: Collection<Model>,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        setSections<V, Model>(data, extra)
        notifyDataSetChanged()
    }
    fun  removeItemFromSectionsAndNotify(
        index: Int,
    ) {
        sections.removeAt(index)
        notifyDataSetChanged()
    }

    inline fun <reified V, reified Model : Any> setSectionsAndNotifyRange(
        data: Collection<Model>,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        val prevSize = itemCount
        setSections<V, Model>(data, extra)
        val newSize = itemCount
        notifyItemRangeChanged(0, newSize)
        if (prevSize > newSize) {
            notifyItemRangeRemoved(newSize, prevSize - newSize)
        }
    }


    inline fun <reified V, reified Model : Any> addSections(
        data: Collection<Model>,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        val type = viewType<V>()
        sections.addAll(data.map { Section(type, it, extra) })
    }

    inline fun <reified V> addView(
    ) where V : View {
        val type = viewType<V>()
        sections.add(Section(viewType<V>(), null, null))
    }

    inline fun <reified V, reified Model : Any> addSectionsAndNotify(
        data: Collection<Model>,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        addSections<V, Model>(data, extra)
        notifyDataSetChanged()
    }

    inline fun <reified V, reified Model : Any> addSectionsAndNotifyRange(
        data: Collection<Model>,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        addSections<V, Model>(data, extra)
        notifyItemRangeChanged(itemCount - data.size, itemCount)
    }
    inline fun <reified V, reified Model : Any> addSectionsAndNotifyCompleteRange(
        data: Collection<Model>,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        addSections<V, Model>(data, extra)
        notifyItemRangeChanged(itemCount - data.size, itemCount)
    }


    inline fun <reified V, reified Model : Any?> setSection(
        position: Int,
        data: Model,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        sections[position] = Section(viewType<V>(), data, extra)
    }

    inline fun <reified V, reified Model : Any?> setSectionAndNotify(
        position: Int,
        data: Model,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        setSection<V, Model>(position, data, extra)
        notifyItemChanged(position)
    }

    fun updateSection(
        position: Int,
    ) {
        notifyItemChanged(position)
        Handler(Looper.getMainLooper()).post {
            notifyItemChanged(position)
        }
    }
    
    inline fun <reified V, reified Model : Any> addSection(
        data: Model,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        sections.add(Section(viewType<V>(), data, extra))
    }

    inline fun <reified V, reified Model : Any> addSectionAndNotify(
        data: Model,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        addSection<V, Model>(data, extra)
        notifyItemInserted(sections.size - 1)
    }

    inline fun <reified V, reified Model : Any?> createEmptySection(
        data: Model?,
        extra: Any? = null
    ): Section<Model?> where V : View, V : GenericAdapterView<Model> =
        Section(viewType<V>(), data, extra)

    inline fun <reified V, reified Model : Any> createSection(
        data: Model,
        extra: Any? = null
    ): Section<Model> where V : View, V : GenericAdapterView<Model> =
        Section(viewType<V>(), data, extra)


    inline fun <reified V, reified Model : Any> addSection(
        position: Int,
        data: Model,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        sections.add(position, Section(viewType<V>(), data, extra))
    }

    inline fun <reified V, reified Model : Any?> addEmpotySection(
        position: Int,
        data: Model? = null,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        val type = viewType<V>()
        if (sections.find { it.type == type } != null) {
            sections.add(position, Section(viewType<V>(), data, extra))
            val index = sections.indexOfLast { it.type == type }
            providers.delete(index)
            sections.removeAt(index)
            notifyItemRangeChanged(index-1,1)
        } else {
            sections.add(position, Section(viewType<V>(), data, extra))
            notifyItemInserted(position)
        }
    }

    inline fun <reified V, reified Model : Any> addSectionAndNotify(
        position: Int,
        data: Model,
        extra: Any? = null
    ) where V : View, V : GenericAdapterView<Model> {
        addSection<V, Model>(position, data, extra)
        notifyItemInserted(position)
    }


    inline fun <reified V, reified Model : Any> createSections(
        data: Collection<Model>,
        extra: Any? = null
    ): List<Section<Model>> where V : View, V : GenericAdapterView<Model> =
        data.map { Section(viewType<V>(), it, extra) }


    fun createSection(
        cls: Class<*>,
        data: Any,
        extra: Any? = null
    ): Section<*> =
        Section(viewType(cls), data, extra)


    fun clearData() {
        sections.clear()
    }

    fun clearDataAndNotify() {
        sections.clear()
        providers.clear()
        notifyDataSetChanged()
    }

    inline fun <reified V : View> clearViews() {
        val type = viewType<V>()
        sections.removeAll { it.type == type }
    }

    inline fun <reified V : View> findIndexItem(): Int {
        val type = viewType<V>()
        var item = 0
        sections.forEachIndexed { index, section ->
            if (section.type == type) {
                item = index
            }
        }
        return item
    }

    inline fun <reified V : View> findIndexItemNew(): Int? {
        val type = viewType<V>()
        var item: Int? = null
        sections.forEachIndexed { index, section ->
            if (section.type == type) {
                item = index
            }
        }
        return item
    }

    inline fun <reified V : View> clearSectionsWithTypeAndNotify() {
        clearViews<V>()
        notifyItemRemoved(viewType<V>(false))
        providers.delete(viewType<V>(false))
    }

    inline fun <reified T> sectionsWithType(): List<Section<T>> =
        sections.mapIndexed { index, section -> section.index = index; section }
            .filter { it.data is T }.map { it as Section<T> }

}

fun AppGenericAdapter.setSectionsInViewsListAndTryToRecycle(
    parent: ViewGroup,
    lpProvider: (AppGenericAdapter.(Section<*>, Int) -> ViewGroup.LayoutParams)? = null
) {
    for (i in 0 until sections.size) {
        val section = sections[i]
        if (i < parent.childCount) {
            try {
                val child = parent.getChildAt(i) as GenericAdapterView<Any>
                child.onBind(section.data as Any, i, section.extraObject)
            } catch (e: Exception) {
                parent.removeAllViews()
                setSectionsInViewsListAndTryToRecycle(parent)
            }
        } else {
            val vh = onCreateViewHolder(parent, section.type)
            if (vh.itemView is GenericAdapterView<*>) {
                (vh.itemView as GenericAdapterView<Any>).onBind(
                    section.data!!,
                    i,
                    section.extraObject
                )
            }
            if (lpProvider != null) {
                vh.itemView.layoutParams = lpProvider(section, i)
            }
            parent.addView(vh.itemView)
        }
    }
    if (parent.childCount - sections.size > 0) {
        parent.removeViews(sections.size, parent.childCount - sections.size)
    }
}

This is a Generic adapter class you can use in all project instances of a simple adapter. you just add an item and pass to the generic class to create your list.

How to User First of all, you must add this class to your project.

abstract class BaseCustomView<viewDataBinding : ViewDataBinding> @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
    lateinit var binding: viewDataBinding

    init {
        this.inflate(context, attrs)
    }

    @CallSuper
    protected open fun inflate(context: Context, attrs: AttributeSet?){

        val bindingClass = javaClass.findGenericWithType<viewDataBinding>(ViewDataBinding::class.java)

        if(isInEditMode){
            if(bindingClass != null){
                val resId = bindingClass.layoutId(context)
                LayoutInflater.from(context).inflate(resId, this, true)
            }
        } else {
            binding = bindingClass?.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)?.invoke(null, LayoutInflater.from(context), this, true) as viewDataBinding
            binding.setView(this)
        }
    }


}

after that, your item (VH), must inheritance this class like this

class YourItem @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : BaseCustomView<YouBinding>(context, attrs, defStyleAttr),
    GenericAdapterView<Model> {
    override fun onBind(model: Model, position: Int, extraObject: Any?) {
    }
}

and finally, append an item to the adapter

private val adapter = AppGenericAdapter().apply {
    provider { context ->
        YourItem (context,)// if you want to pass extra data you can pass data as arg
    }
    provider { context ->
        YourItemButton(context,)// if you want to pass extra data you can pass data as arg
    }
}

binding.recycler.adapter = adapter
adapter.addSectionsAndNotify<YourItem , Model>(data)
// you can check with if the last item add this item
adapter.addSectionsAndNotify<YourItemButton , Model>(data)
  • Related