5

I have a StaggeredGrid in a RecyclerView and I am trying to dynamically set the image height in the onBindViewHolder. I thought the StaggeredGrid was supposed to handle all this automatically, so I set android:layout_width="match_parent and android:scaleType="fitStart on the ImageView, but there is lots of gaps around the image.

Since the StaggeredGrid isn't living up to it, I'm trying to help it a bit by defining the image height dynamically in the onBindViewHolder. I have the width and height of the image ahead of time, but the cell width of the grid isn't available. I tried holder.cardView.getLayoutParams().width and getMeasuredWidth(), but they both return zero or small numbers. I know there are other places the cell width would be available, but I really need it in the onBindViewHolder event so I can set and adjust the image.

Any ideas on how to achieve this by leveraging the StaggeredGrid? Any advice or experience is appreciated!

In my activity is this:

mLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);

In my adapter:

@Override
public void onBindViewHolder(MyAdapter.MyViewHolder holder, int position) {
    MyModel item = mData.get(position);

    int imageWidth = item.getFeatured_image().getWidth();
    int imageHeight = item.getFeatured_image().getHeight();
    int cellWidth = 540; // <==== how to find dynamically??!!

    ViewGroup.LayoutParams imageLayoutParams = holder.thumbnailImageView.getLayoutParams();
    imageLayoutParams.width = cellWidth;
    imageLayoutParams.height = imageHeight * cellWidth / imageWidth;
    holder.thumbnailImageView.setLayoutParams(imageLayoutParams);

    MediaHelper.displayImage(item, holder.thumbnailImageView);
}

In my layout:

<android.support.v7.widget.CardView
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:id="@+id/row_my_card"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    card_view:cardCornerRadius="2dp">

    <ImageView
        android:id="@+id/row_my_thumbnail_image"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="top"
        android:scaleType="fitStart" />

    <TextView
        android:id="@+id/row_my_title_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="#CCFFFFFF"
        android:textStyle="bold"
        android:padding="5dp" />
</android.support.v7.widget.CardView>

I also tried to add this which does give me the cell width, but the onGlobalLayout event gets triggered way after the image is set and adjusted in the onBindViewHolder event so it's too late.

@Override
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // create a new view
    final View view = LayoutInflater.from(parent.getContext())
        .inflate(R.layout.row_my_post, parent, false);

    final MyViewHolder viewHolder = new MyViewHolder(view);

    ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
    if (viewTreeObserver.isAlive()) {
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                int viewWidth = view.getWidth();
                int viewHeight = view.getHeight();
            }
        });
    }

    return viewHolder;
}

Here is the gaps which are random due to the various image sizes. What I would like is for ALL images to be the width of the cell and let the height of the image and cell adjust dynamically:

enter image description here

TruMan1
  • 33,665
  • 59
  • 184
  • 335
  • Any chance at all you could add a screenshot of this gap so that it would be obvious that the gap is not due to inherent padding inside the cardview and is actually a bigger gap than that? – kha Jul 01 '15 at 12:12
  • Ok thank you. Next question: The gap you're referring to is for the image itself or for the spacing between the cards? – kha Jul 01 '15 at 12:17
  • Gaps to the right of the image. See "7 ways to use cashew milk", there's a gap to the right because it couldn't fill up the width. – TruMan1 Jul 01 '15 at 12:20
  • Btw, using my hard coded `540` cell width number in my `onBindViewHolder` sample above, all the image width fill up the card nicely. I just can't find the actual cell width dynamically there instead of hard coding it (540 happens to be the cell width of this device/simulator... I guess it's half the resolution size because of my `StaggeredGridLayoutManager` set to 2 columns). – TruMan1 Jul 01 '15 at 12:22
  • use this param in your ImageView---> android:adjustViewBounds="true" – Chaudhary Amar Jul 01 '15 at 12:55
  • @AmarbirSingh WOW that simple param worked!! Pls add as an answer and I will accept. – TruMan1 Jul 01 '15 at 13:40
  • @TruMan1 I have posted this please upvote my answer!!! – Chaudhary Amar Jul 01 '15 at 13:47

3 Answers3

13

Try This:- add this in your ImageView--> android:adjustViewBounds="true"

<android.support.v7.widget.CardView
 xmlns:card_view="http://schemas.android.com/apk/res-auto"
 android:id="@+id/row_my_card"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_margin="5dp"
 card_view:cardCornerRadius="2dp">

<ImageView
    android:id="@+id/row_my_thumbnail_image"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="top"
    android:adjustViewBounds="true"/>

<TextView
    android:id="@+id/row_my_title_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:background="#CCFFFFFF"
    android:textStyle="bold"
    android:padding="5dp" />
 </android.support.v7.widget.CardView>
Chaudhary Amar
  • 836
  • 8
  • 20
4

Instead of getting the view height and width from onCreateViewHolder get it from onBindViewHolder as below:

@Override
public void onBindViewHolder(final MyAdapter.MyViewHolder holder, int position) 
{
    holder.itemView.post(new Runnable()
    {
        @Override
        public void run()
        {
            MyModel item = mData.get(position);
            int imageWidth = item.getFeatured_image().getWidth();
            int imageHeight = item.getFeatured_image().getHeight();
            int cellWidth = 540; // <==== how to find dynamically??!!

            cellWidth = holder.itemView.getWidth();// this will give you width dynamically

            ViewGroup.LayoutParams imageLayoutParams = holder.thumbnailImageView.getLayoutParams();
            imageLayoutParams.width = cellWidth;
            imageLayoutParams.height = imageHeight * cellWidth / imageWidth;
            holder.thumbnailImageView.setLayoutParams(imageLayoutParams);
            MediaHelper.displayImage(item, holder.thumbnailImageView);
        }
    });
}

I hope this will help you!!

Amrut Bidri
  • 6,276
  • 6
  • 38
  • 80
  • This is what I tried before but the width returns 0 because it is not rendered yet. – TruMan1 Jul 01 '15 at 13:41
  • 1
    it is rendered when u post to get the width. it works. – Amrut Bidri Jul 01 '15 at 14:01
  • You're right it does work! I didn't notice the post/runnable method. I went with the layout solution since it is simple and more natural to the layout. Thx for sharing this as I didn't know you could do that! – TruMan1 Jul 01 '15 at 14:14
  • It's 2022. I tried many answers. but none of them was accurate. This one just did the job as expected – Amit Kundu Jun 13 '22 at 16:28
0

The cell's width can be found at activity/fragment level and passed to the adapter before rendering time. Use something like this:

this.staggeredGridLayoutManager.getWidth() / this.staggeredGridLayoutManager.getSpanCount()

With this approach, you will be able to compute the cell's height before any rendering and avoid that initial flick introduced by post run approach. Also, the image's aspect ratio will be preserved and you can reserve the required space before image load.

Be also aware that if any margins/paddings are present you should include them into previous calculation. Something like:

(this.staggeredGridLayoutManager.getWidth() - marginsAndOrPaddings) / this.staggeredGridLayoutManager.getSpanCount()

Update: Starting with support packages 23.2.0, and at this point of lifecycle, this.staggeredGridLayoutManager.getWidth() returns 0. The total width can be achieved with this.recyclerView.getWidth()

Carlos Castro
  • 571
  • 4
  • 5