Search code examples
androidkotlinimageviewscrollview

ImageView in LinearLayout in Scrollview with CardStackView and Picasso Picture does not show


I am developing an Android application.

I am using "com.yuyakaido.android:card-stack-view:2.3.4" for card stack library.

The ImageView is in LinearLayout in ScrollView in CardView.

What I want to do is to fit the image in the card's size as per the picture below when the card is loaded at first.

enter image description here

And if you scroll down, it will display some texts as per the picture below

enter image description here

I use Picasso to fit the image to ImageView but the image does not show.

This is the fragment which has the CardStackView

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

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

private fun initCardStackView() {
    manager = CardStackLayoutManager(context)
    manager.setCanScrollVertical(false)
    manager.setSwipeableMethod(SwipeableMethod.Manual)
    adapter = CardStackAdapter(context, createDummyProfiles())
    cardStackView.layoutManager = manager
    cardStackView.adapter = adapter
    cardStackView.itemAnimator = DefaultItemAnimator()
}

This is the Fragment layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/linearLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <com.yuyakaido.android.cardstackview.CardStackView
        android:id="@+id/cardStackView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="80dp"
        android:clipToPadding="false"/>

</LinearLayout>

This is the CardStackAdapter, here I crop and fit the image to ImageView with Piccaso

class CardStackAdapter(private var context: Context?, private var movies: MutableList<Movies>) :
    RecyclerView.Adapter<CardStackAdapter.ViewHolder>() {

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

    }

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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        Picasso.get().load(R.drawable.person3).fit().centerCrop().into(holder.itemView.imageView)
    }

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

    fun setItems(items: MutableList<Profile>) {
        this.movies = items
    }
}

This is the card_view which is the layout for viewHolder of CardStackAdapter

<?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:id="@+id/cardView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?attr/selectableItemBackground"
    android:foreground="?attr/selectableItemBackground"
    app:cardBackgroundColor="@android:color/white"
    app:cardCornerRadius="18dp"
    app:cardPreventCornerOverlap="false"
    app:cardUseCompatPadding="true">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

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

            <ImageView
                android:id="@+id/imageView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/lipsum"/>

        </LinearLayout>

    </ScrollView>

</androidx.cardview.widget.CardView>

When I run the application, I get the card without the image but only texts as per the picture below

enter image description here

How can I display the image in the size of the card when the card is loaded?



------------------------- UPDATE ----------------------------

** NOTE: The image will be retrieved from the server not local drawable. I just used the images in local drawable for testing purpose.

I am not sure why the image does not show. But if I set the height and width of layoutParams of imageView as per code below, then the image shows up.

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    holder.itemView.imageView.layoutParams.height = 1918
    holder.itemView.imageView.layoutParams.width = 970
    Picasso.get().load(R.drawable.person3).fit().centerCrop().into(holder.itemView.imageView)
}

So, now the question is whether I can set the height and width of ImageView equal to the height and width of ScrollView (imageView's parent's parent) or CardView (imageView's parent's parent's parent) NOT LinearLayout (parent) because LinearLayout's height should be "wrap_content".



------------------------- UPDATE ----------------------------

It seems like your solution is based on image's height and width, but I need to fit the image in the CardView or ScrollView

here is my onBindViewHolder code. I just removed Picasso.

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    holder.itemView.imageView.setImageResource(R.drawable.person2)
}

Here is my CardView layout

<?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:id="@+id/cardView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?attr/selectableItemBackground"
    android:foreground="?attr/selectableItemBackground"
    app:cardCornerRadius="18dp"
    app:cardPreventCornerOverlap="false"
    app:cardUseCompatPadding="true">

    <androidx.core.widget.NestedScrollView
        android:fillViewport="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

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

            <ImageView
                android:id="@+id/imageView"
                android:src="@drawable/person2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                android:scaleType="centerCrop"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/lipsum"/>

        </LinearLayout>

    </androidx.core.widget.NestedScrollView>

</androidx.cardview.widget.CardView>

This is what I have got on my AVD. The image does not fill the bottom of CardView because it scales based on its height and width?

enter image description here


Solution

  • What i understand from your updated question you need to to fill the ImageView upto CardView Height and the textView will be visible after scrolling. This Seems tricky to with ScrollView because we somehow need to set height dynamically match_parent won't work with ScrollView.
    I have made a Solution below see if this workout for you. Create a custom Image view and use inside CardView.

    class FillImageView : AppCompatImageView {
    constructor(context: Context?) : super(context!!) {}
    constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) {}
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context!!, attrs, defStyleAttr) {}
    
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val parentWidth = MeasureSpec.getSize(widthMeasureSpec)
        setMeasuredDimension(parentWidth, (parent.parent as View).measuredHeight)
    }
    }
    

    And add the NestedScrolllView below inside CardView

    <androidx.core.widget.NestedScrollView
                android:layout_width="match_parent"
                android:fillViewport="true"
                android:layout_height="match_parent">
            <LinearLayout
                    android:layout_width="match_parent"
                    android:orientation="vertical"
                    android:layout_height="wrap_content">
                <com.ace.myapplication.FillImageView
                        android:layout_width="match_parent"
                        android:src="@drawable/hello"
                        android:adjustViewBounds="true"
                        android:layout_height="match_parent"
                        android:scaleType="centerCrop"/>
                <TextView
                        android:layout_width="match_parent"
                        android:textSize="23sp"
                        android:padding="10dp"
                        android:layout_height="wrap_content"/>
            </LinearLayout>
        </androidx.core.widget.NestedScrollView>
    

    PS- It might be an ugly solution with setting height with (parent.parent as View).measuredHeight but this is all i can thing of right now . love to see some elegant solution for this. I haven't tested it with CardStackView.

    Update Now with RecyclerView the problem is item drawn after the adapter is set and we need to listen for the layoutManager callback of Item drawn since. One way to tackle this without making it complex Set the cadStackView height to the imageView . See the code below adding only essential code.

    class CardStackAdapter(val stackViewHeight:Int, private var movies: MutableList<Moview>) :
        RecyclerView.Adapter<CardStackAdapter.ViewHolder>() {
    
       inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            val imageView:ImageView = itemView.findViewById(R.id.imageView)
            init {
                imageView.layoutParams.height= stackViewHeight
            }
        }
    }
    

    Now in your fragment

    cardStackView.post {
           initCardStackView()
       }
    

    And change the dapter creation inside initCardStackView as follows. Also you do not have to pass Context to adapter each view has a context attached to it . Also do not use FillImageView in this case use normal ImageView in xml layout.

    // Since you are using android:padding it will be same for all sides
        val cardHeight=cardStackView.height- (2*cardStackView.paddingTop)
        val adapter = CardStackAdapter(cardHeight, createDummyProfiles())