Search code examples
androidkotlinandroid-cardviewgridlayoutmanager

How to use Gridlayoutmanager for expandable cards inside a recyclerView


I am trying to make a grid layout with expandable cards, but the problem is that when a card is expanded, its height gets bigger and so does the height of the other cards in the row (to match the height of the first card), but when the card is collapsed back, the height of all the cards does not change as if they were expanded. Anyone knows what could be the problem?

EDIT :

recyclerview_item.xml

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

<com.google.android.material.card.MaterialCardView 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:id="@+id/cardView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardBackgroundColor="@color/misty_rose"
    android:layout_margin="8dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- Media -->
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:layout_gravity="center_horizontal"
            android:padding="8dp"
            android:contentDescription="Photo"
            android:src="@drawable/unsplash"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:background="@color/isabelline"
            />

        <!-- Title, secondary and supporting text -->

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/misty_rose"
            app:layout_constraintTop_toBottomOf="@+id/imageView"
            android:padding="8dp">

            <TextView
                android:id="@+id/textViewCode"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Code"
                android:textAppearance="?attr/textAppearanceHeadline6"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <androidx.appcompat.widget.AppCompatImageButton
                android:id="@+id/iconExpandCard"
                android:layout_height="wrap_content"
                android:layout_width="wrap_content"
                android:padding="8dp"
                android:src="@drawable/ic_baseline_expand_more_36"
                android:background="?attr/selectableItemBackgroundBorderless"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                />

            <TextView
                android:id="@+id/textViewDescription"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="Description"
                android:textAppearance="?attr/textAppearanceBody1"
                android:textColor="?android:attr/textColorSecondary"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/textViewCode" />

            <TextView
                android:id="@+id/textViewPrice"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="Price"
                android:textAppearance="?attr/textAppearanceBody1"
                android:textColor="?android:attr/textColorSecondary"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/textViewDescription"/>

            <TextView
                android:id="@+id/textViewComment"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="Comment"
                android:textAppearance="?attr/textAppearanceBody1"
                android:textColor="?android:attr/textColorSecondary"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/textViewPrice"
                android:visibility="gone"/>

            <!-- Buttons -->
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginTop="8dp"
                android:orientation="horizontal"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/textViewComment">

                <com.google.android.material.button.MaterialButton
                    android:id="@+id/buttonMinusArticle"
                    style="?attr/borderlessButtonStyle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="16dp"
                    android:width="88dp"
                    android:minWidth="40dp"
                    android:backgroundTint="@color/purple_200"
                    android:text="@string/minus"
                    android:textColor="@color/white" />

                <EditText
                    android:id="@+id/editNumberOfProducts"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginEnd="16dp"
                    android:inputType="numberDecimal|number"
                    android:text="@string/zero"
                    android:textAppearance="?attr/textAppearanceBody1"
                    android:textColor="?android:attr/textColorSecondary"
                    android:textSize="18sp" />

                <com.google.android.material.button.MaterialButton
                    android:id="@+id/buttonPlusArticle"
                    style="?attr/borderlessButtonStyle"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:width="88dp"
                    android:minWidth="40dp"
                    android:backgroundTint="@color/purple_200"
                    android:text="@string/plus"
                    android:textColor="@color/white" />
            </LinearLayout>

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>

</com.google.android.material.card.MaterialCardView>

And in ProductListAdapter.kt (the important part is in expandButton.setOnClickListener() ):

class ProductListAdapter() : ListAdapter<Product, ProductListAdapter.ProductViewHolder>(ProductsComparator()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
        return ProductViewHolder.create(parent)
    }

    override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
        val current = getItem(position)
        holder.bind(current!!)
    }

    class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val productItemView: TextView = itemView.findViewById(R.id.textViewCode)
        private val productDescription : TextView = itemView.findViewById(R.id.textViewDescription)
        private val productPrice : TextView = itemView.findViewById(R.id.textViewPrice)

        fun bind(product: Product) {
            productItemView.text = product.code
            //productDescription.text = product.description
            //productPrice.text = "Price " + product.client_price.toString() 
        }

        companion object {
           
            val mapOfProducts :HashMap<String,  Int> = hashMapOf<String, Int>()

            fun create(parent: ViewGroup): ProductViewHolder {

                val view: View = LayoutInflater.from(parent.context)
                    .inflate(R.layout.recyclerview_item, parent, false)

                val minusButton : Button = view.findViewById(R.id.buttonMinusArticle)
                val plusButton : Button = view.findViewById(R.id.buttonPlusArticle)
                val productItemViewCode: TextView = view.findViewById(R.id.textViewCode)
                val expandButton : androidx.appcompat.widget.AppCompatImageButton = view.findViewById(R.id.iconExpandCard)
                val commentView :  TextView = view.findViewById(R.id.textViewComment)
                val cardView : CardView = view.findViewById(R.id.cardView)

                expandButton.setOnClickListener{
                    if (commentView.visibility == View.GONE){
                        TransitionManager.beginDelayedTransition(cardView, AutoTransition())
                        commentView.visibility = View.VISIBLE
                        expandButton.setImageResource(R.drawable.ic_baseline_expand_less_36)
                    } else {
                        TransitionManager.beginDelayedTransition(cardView, AutoTransition())
                        commentView.visibility = View.GONE
                        expandButton.setImageResource(R.drawable.ic_baseline_expand_more_36)
                    }
                }

                val editNumberOfProducts: EditText = view.findViewById(R.id.editNumberOfProducts)
                editNumberOfProducts.doAfterTextChanged {
                    val code = productItemViewCode.text.toString()

                    if (it.isNullOrBlank()) {
                        modifyText("0", view)
                        mapOfProducts.remove(code)
                        return@doAfterTextChanged
                    }
                    val originalText = it.toString()
                    try {
                        val number = originalText.toInt()
                        val numberText = originalText.toInt().toString()
                        if (originalText != numberText) {
                            modifyText(numberText, view)
                        }
                        if (number > 0) {
                            mapOfProducts[code] = number
                            d("CodeOfView", "$code $number")
                        }else {
                            mapOfProducts.remove(code)
                        }
                    } catch (e: Exception) {
                        modifyText("0", view)
                        mapOfProducts.remove(code)
                    }
                }

                minusButton.setOnClickListener {
                    var number = editNumberOfProducts.text.toString().toInt()
                    if (number>0) {
                        number -= 1
                        modifyText(number.toString(), view)
                    }
                }

                plusButton.setOnClickListener {
                    //val code = productItemViewCode.text.toString()
                    var number = editNumberOfProducts.text.toString().toInt()
                    number += 1
                    modifyText(number.toString(), view)
                }

                return ProductViewHolder(view)
            }
            private fun modifyText(numberText: String, view: View) {
                val editNumberOfProducts = view.findViewById<EditText>(R.id.editNumberOfProducts)
                editNumberOfProducts.setText(numberText)
                editNumberOfProducts.setSelection(numberText.length)
            }
        }

    }

    class ProductsComparator : DiffUtil.ItemCallback<Product>() {
        override fun areItemsTheSame(oldItem: Product, newItem: Product): Boolean {
            return oldItem === newItem
        }

        override fun areContentsTheSame(oldItem: Product, newItem: Product): Boolean {
            return oldItem.code == newItem.code
        }
    }

}

Example with images of the problem


Solution

  • I was facing a same issue, in my case it was vertical expandable cards and I managed to solve it by using

    Adapter.notifyDataSetChanged()

    in the right place.