Publicado en Android

Implementar una vista de lista como Google Play Store, Android Kotlin


Apuntes de crear un layout que combine lista vertical con desplazamiento de contenido horizontal, para mostrar elementos agrupados y con el botón ver más, más o menos como el listado de aplicaciones de Google play,

Concepto visual

Dependiendo del ancho del dispositivo se mostrará más tarjetas o no, algunas solo se verá parcialmente asomara desde el borde.

Entonces, para este ancho de dispositivo en particular, podemos mostrar dos tarjetas. Para ver la tercera carta y más, debemos desplazarnos horizontalmente.

Añadir botón de mostrar más (See More, See All, View All, View More, Show More), que al pulsar se muestre una lista de elementos.

Layouts

Empezar por crear el layout que agrupa los elementos, con el título botón y lista horizontal., section_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

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

        <TextView
            android:id="@+id/itemTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true"
            android:layout_centerInParent="true"
            android:layout_marginStart="16dp"
            android:layout_marginTop="24dp"
            android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
            tools:text="Section 0" />

        <Button
            android:id="@+id/btn_see_more"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_alignParentEnd="true"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:text="See more" />

    </RelativeLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_margin="0dp" />

</LinearLayout>

La vista para rempresentar los elementos dentro de la lista horizontal item_square_card.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:cardCornerRadius="5dp"
    android:layout_marginTop="8dp"
    android:layout_marginBottom="8dp"
    app:cardUseCompatPadding="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:foreground="?attr/selectableItemBackground"
        android:orientation="vertical"
        android:padding="0dp">

        <ImageView
            android:id="@+id/icon"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_gravity="center_horizontal"
            android:scaleType="fitCenter"
            android:src="@mipmap/ic_launcher_round" />

        <TextView
            android:id="@+id/text1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="5dp"
            android:text="Sample title"
            android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
            android:textColor="@android:color/black" />

    </LinearLayout>

</androidx.cardview.widget.CardView>

Modelos de datos

Ahora crear los modelos de datos SectionDataModel para la sección que agrupa y SingleItemModel

data class SectionDataModel(
    var uid: String,
    var headerTitle: String,
    var category: Int = 0,
    var allItemsInSection: MutableList<SingleItemModel>? = null
)

data class SingleItemModel(
    var uid: String,
    var title: String,
)

Adaptadores usando ListAdapter

Para los adaptadores para controlar el recyclerview se usará un ListAdapter, el adaptador para controlar el grupo de elementos SectionDataAdapter y el de la lista en horizontal SingleItemAdapter

Código kotlin de SectionDataAdapter.kt

class SectionDataAdapter(private val itemClickListener: OnItemClickListener) :
    ListAdapter<SectionDataModel, SectionDataAdapter.MyViewHolder>(DiffCallback()) {


    private class DiffCallback : DiffUtil.ItemCallback<SectionDataModel>() {
        override fun areItemsTheSame(
            oldItem: SectionDataModel,
            newItem: SectionDataModel
        ): Boolean {
            return oldItem.uid == newItem.uid
        }

        override fun areContentsTheSame(
            oldItem: SectionDataModel,
            newItem: SectionDataModel
        ): Boolean {
            return oldItem == newItem
        }

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val v = LayoutInflater.from(parent.context).inflate(R.layout.section_item, parent, false)
        return MyViewHolder(v)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bind(getItem(position), itemClickListener)
    }


    class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bind(item: SectionDataModel, clickListener: OnItemClickListener) {
            itemView.itemTitle.text = item.headerTitle
            itemView.btn_see_more.setOnClickListener {
                clickListener.onSeeMoreClick(item)
            }

            itemView.recyclerView2.setHasFixedSize(true)
            val adapter = SingleItemAdapter {
                clickListener.onItemClick(it)
            }

            adapter.submitList(item.allItemsInSection)
            itemView.recyclerView2.layoutManager =
                LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false)
            itemView.recyclerView2.adapter = adapter

        }

    }

    interface OnItemClickListener {
        fun onSeeMoreClick(item: SectionDataModel)
        fun onItemClick(item: SingleItemModel)
    }

    companion object {
        private val TAG = SectionDataAdapter::class.java.simpleName
    }
}

Código kotlin de SingleItemAdapter.kt

class SingleItemAdapter(private val itemClickListener: (SingleItemModel) -> Unit) :
    ListAdapter<SingleItemModel, SingleItemAdapter.MyViewHolder>(DiffCallback()) {


    private class DiffCallback : DiffUtil.ItemCallback<SingleItemModel>() {
        override fun areItemsTheSame(
            oldItem: SingleItemModel,
            newItem: SingleItemModel
        ): Boolean {
            return oldItem.uid == newItem.uid
        }

        override fun areContentsTheSame(
            oldItem: SingleItemModel,
            newItem: SingleItemModel
        ): Boolean {
            return oldItem == newItem
        }

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val v = LayoutInflater.from(parent.context).inflate(R.layout.item_square_card, parent, false)
        return MyViewHolder(v)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bind(getItem(position), itemClickListener)
    }


    class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bind(item: SingleItemModel, clickListener: (SingleItemModel) -> Unit) {
            Log.d(TAG, "bind() called with: item = [$item], clickListener = [$clickListener]")
            itemView.text1.text = item.title

            itemView.setOnClickListener { clickListener(item) }
        }

    }

    companion object {
        private val TAG = SingleItemAdapter::class.java.simpleName
    }
}

Implementación

Ahora se implementará el adaptador al recyclerview y generar los datos de ejemplo

class FirstFragment : Fragment() {

    private var allSampleData: MutableList<SectionDataModel> = ArrayList()

    private val mClicklistener: SectionDataAdapter.OnItemClickListener =
        object : SectionDataAdapter.OnItemClickListener {
            override fun onSeeMoreClick(item: SectionDataModel) {
                Toast.makeText(
                    requireContext(),
                    "see more category:" + item.category,
                    Toast.LENGTH_SHORT
                )
                    .show()
            }

            override fun onItemClick(item: SingleItemModel) {
                Toast.makeText(requireContext(), "see item:" + item.title, Toast.LENGTH_SHORT)
                    .show()
            }
        }


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

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

        allSampleData = ArrayList()
        createDummyData()
        setupRecyclerView()


    }

    private fun setupRecyclerView() {
        recyclerView.setHasFixedSize(true)
        val adapter = SectionDataAdapter(mClicklistener)

        adapter.submitList(allSampleData)
        recyclerView.layoutManager =
            LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
        recyclerView.adapter = adapter
    }

    private fun setupRecyclerView2() {
        recyclerView.setHasFixedSize(true)
        val adapter = SingleItemAdapter {
            Toast.makeText(requireContext(), "see item" + it.title, Toast.LENGTH_SHORT).show()
        }
        adapter.submitList(allSampleData.first().allItemsInSection)
        recyclerView.layoutManager =
            LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
        recyclerView.adapter = adapter
    }


    private fun createDummyData() {
        for (i in 1..5) {
            val singleItems = ArrayList<SingleItemModel>()
            for (j in 0..5) {
                singleItems.add(
                    SingleItemModel(
                        UUID.randomUUID().toString(),
                        "Item " + i + "x" + j
                    )
                )
            }

            val dm = SectionDataModel(
                UUID.randomUUID().toString(),
                "Section $i",
                i,
                singleItems

            )
            allSampleData.add(dm)
        }
    }

}

Demostración

Se generará una vista combinando un lista en vertical con cada sección con su lista horizontal, como la imágen

Autor:

Desarrollador freelance programador apasionado por el arte de programar, amante del auto aprendizaje y interesado por la tecnología en general.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios .