Search code examples
androidandroid-layoutkotlinandroid-recyclerviewandroid-imageview

RecyclerView Item Size changing while scrolling


When first time recyclerView Load It will show all proper. but after scrolling, layout item size changed.

enter image description here

this is my item_layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@drawable/item_border"
    android:layout_margin="@dimen/item_margin_2">
    <ImageView
        android:id="@+id/imageProduct"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/app_name"
        android:scaleType="centerInside"
        android:src="@drawable/ic_launcher_foreground"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
         />

    <TextView
        android:id="@+id/textProductTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:textSize="@dimen/title_textSize"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/imageProduct"
        tools:text="TubeLight" />

    <TextView
        android:id="@+id/textProductDetailSort"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:textColor="@android:color/black"
        android:textSize="@dimen/detail_textSize"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textProductTitle"
        tools:text="warranty: 100 days" />


    <LinearLayout
        android:id="@+id/linerCartItem"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textProductDetailSort">

        <ImageButton
            android:id="@+id/buttonRemove"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/item_border_small"
            android:src="@drawable/ic_outline_remove_24"/>

        <TextView
            android:id="@+id/textNumberItem"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/colorPrimaryLight"
            android:textSize="@dimen/detail_small_textSize"
            android:padding="@dimen/item_padding"
            android:textColor="@color/black"
            android:layout_margin="@dimen/item_margin_3"
            android:text="0"/>

        <ImageButton
            android:id="@+id/buttonAdd"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/item_border_small"
            android:src="@drawable/ic_baseline_add_24"/>

    </LinearLayout>


    <TextView
        android:id="@+id/textViewProductPrice"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/linerCartItem"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginStart="@dimen/item_margin"
        android:text="@string/title_price"/>


</androidx.constraintlayout.widget.ConstraintLayout>

this is adapter class

class ProductListAdapter(context: Context, private val cellClickListener: HomeFragment) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    interface CellClickListener {
        fun onCellClickListener(productModel: ProductModel)
        fun onNextClicked()
        fun onPrevClicked()
        fun onAddClicked(productId: String): Int
        fun onRemoveClicked(productId: String): Int
    }

    val FOOTER_TYPE : Int = 1
    val HEADER_TYPE : Int = 2
    var context : Context = context

    private var listOfProducts = listOf<ProductModel>()


    override fun getItemViewType(position: Int): Int {

         if (position ==  listOfProducts.size) {

             return FOOTER_TYPE
         }
//         else if(position == 0){
//            return HEADER_TYPE
//         }
        return 0

    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

        if (viewType == FOOTER_TYPE){
            return ProductListViewHolderFooter(
                LayoutInflater.from(parent.context).inflate(
                    R.layout.item_product_footer,
                    parent, false
                )
            )
        }else if(viewType == HEADER_TYPE){
            return ProductListViewHolderHeader(
                LayoutInflater.from(parent.context).inflate(
                    R.layout.item_product_header,
                    parent, false
                )
            )
        }
        else{
            return ProductListViewHolder(
                context,
                LayoutInflater.from(parent.context).inflate(
                    R.layout.item_product,
                    parent,
                    false
                )
            )
        }

    }

    override fun getItemCount(): Int = listOfProducts.size + 1

    override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
        try {
            if (viewHolder is ProductListViewHolder) {
                val vh: ProductListViewHolder = viewHolder as ProductListViewHolder
                vh.bindView(listOfProducts[position], cellClickListener)

            } else if (viewHolder is ProductListViewHolderFooter) {
                val vh: ProductListViewHolderFooter = viewHolder as ProductListViewHolderFooter
                vh.bindViewFooter(listOfProducts[position], cellClickListener)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }


    }

    fun setProductList(listOfProducts: List<ProductModel>) {

        this.listOfProducts = listOfProducts
        notifyDataSetChanged()
    }
}

and this is holder

class ProductListViewHolder(context: Context,itemView: View) : RecyclerView.ViewHolder(itemView){

   
    var sharedPreferences: SharedPreferences = context.getSharedPreferences(utils.PREFERENCE_FILE_NAME,
        Context.MODE_PRIVATE)
  

    fun bindView(productModel: ProductModel, cellClickListener: HomeFragment) {
        itemView.textProductTitle.text = productModel.productTitle
        itemView.textProductDetailSort.text = productModel.productDetailSort


        itemView.textViewProductPrice.text = context.getString(R.string.title_price) + ": " + productModel.price

       
        itemView.textNumberItem.text = cart

        Glide.with(itemView.context).load(productModel.productImageUrl!!).into(itemView.imageProduct)

         itemView.imageProduct.setOnClickListener{
             cellClickListener.onCellClickListener(productModel)
         }

         itemView.buttonRemove.setOnClickListener {
             val cart = cellClickListener.onRemoveClicked(productModel.productId)
             itemView.textNumberItem.text = cart.toString()
         }

         itemView.buttonAdd.setOnClickListener {
             val cart = cellClickListener.onAddClicked(productModel.productId)
             itemView.textNumberItem.text = cart.toString()
         }

    }


}

Solution

  • The problem caused by not having any control over the height of the ImageView which leads to varying sizes in the view-recycling process. To fix this issue, you need to apply a bit of change in the imageProduct attributes.

    First, as you bound the imageProduct's start and end to the parent's start and end, you should set the view width to 0dp to be sure it fills the whole space.

    Second, set adjustViewBounds attribute to true. It leads to adjusting the ImageView bounds to preserve the aspect ratio of its drawable.

    Third, set layout_constrainedHeight attribute to true. It ensures that the height of the view remains constrained and also lets view height remains as wrap_content.

    <ImageView
        android:id="@+id/imageProduct"
        android:layout_width="0dp"                      // 1
        android:layout_height="wrap_content"
        android:contentDescription="@string/app_name"
        android:scaleType="centerInside"
        android:src="@drawable/ic_launcher_foreground"
        android:adjustViewBounds="true"                 // 2
        app:layout_constrainedHeight="true"             // 3
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
    

    In addition, if you want to always show the image with a specific aspect ratio, you'd be able to achieve it by changing the height to 0dp also defining the ratio using layout_constraintDimensionRatio attribute, like the following:

    <ImageView
        android:id="@+id/imageProduct"
        android:layout_width="0dp"                     
        android:layout_height="0dp"                     // setting the height constrainted
        android:contentDescription="@string/app_name"
        android:scaleType="centerInside"
        android:src="@drawable/ic_launcher_foreground"
        android:adjustViewBounds="true"      
        app:layout_constraintDimensionRatio="1:1"       // to show as a square  
        app:layout_constrainedHeight="true"             
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />