90

I have a listView with an adapter that contains ImageView of variable size (width and height). I need resize the pictures load with Picasso to the max width of layout and a variable height given by the aspect ratio of the picture.

I have checked this question: Resize image to full width and fixed height with Picasso

The fit() works but I haven't found nothing to keep the aspect ratio of the picture.

This code partially works if I fixed the height in the layout of the adapter:

Picasso.with(this.context).load(message_pic_url)
.placeholder(R.drawable.profile_wall_picture)
.fit().centerInside()
.into(holder.message_picture);

But it generates blank spaces between the pictures of the listView because the pictures may be that not have that height.

starball
  • 20,030
  • 7
  • 43
  • 238
wendigo
  • 1,973
  • 2
  • 17
  • 21

13 Answers13

96

As of Picasso 2.4.0, this operation is now directly supported. Simply add a .resize() request with one of the dimensions as 0. For example, to have a variable width, your call would become:

Picasso.with(this.context)
       .load(message_pic_url)
       .placeholder(R.drawable.profile_wall_picture)
       .resize(0, holder.message_picture.getHeight()),
       .into(holder.message_picture);

Note that this call uses .getHeight() and therefore assumes the message_picture has already been measured. If that isn't the case, such as when you have inflated a new view in a ListAdapter, you can delay this call until after measurement by adding an OnGlobalLayoutListener to the view:

holder.message_picture.getViewTreeObserver()
      .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            // Wait until layout to call Picasso
            @Override
            public void onGlobalLayout() {
                // Ensure we call this only once
                imageView.getViewTreeObserver()
                         .removeOnGlobalLayoutListener(this);


                Picasso.with(this.context)
                       .load(message_pic_url)
                       .placeholder(R.drawable.profile_wall_picture)
                       .resize(0, holder.message_picture.getHeight())
                       .into(holder.message_picture);
            }
        });
George Hilliard
  • 15,402
  • 9
  • 58
  • 96
  • 9
    with this solution I receive the `java.lang.IllegalArgumentException: At least one dimension has to be positive number.` error on rotation, this is in the fragment, any ideas as to why this may happen? – Lukasz 'Severiaan' Grela Nov 26 '14 at 13:27
  • I have added check for the dimensions `> 0` and if so then `removeOnGlobalLayoutListener` and `Picasso` is called and seems to be running fine. – Lukasz 'Severiaan' Grela Nov 26 '14 at 13:36
  • 1
    Hum, If i add this check, i have not this problem anymore but image is not resize ... – mrroboaat Dec 21 '14 at 08:34
  • 2
    @Lukasz'Severiaan'Grela I had the same problem. To fix this example to match the original question you have to turn the arguments around: `.resize(holder.message_picture.getWidth(), 0)` – Christiaan Feb 03 '15 at 10:55
  • 5
    You gave me the idea. Thanks. To those want to have full width image with variable height, use: ```Display display = getWindowManager().getDefaultDisplay(); Point size = new Point(); display.getSize(size); int width = size.x;``` ```.resize(width, 0)``` – fahrulazmi Apr 08 '15 at 16:12
  • Why don't you just use `holder.message_picture.post(new Runnable() { @Override public void run() { creator.resize(holder.message_picture.getWidth(), 0).into(holder.message_picture); } });` ? – keybee Oct 25 '15 at 18:08
  • 1
    I used this method, when you take the app to background and come back, `onGlobalLayout()` is not called and image won't show up. – Gokhan Arik Apr 12 '16 at 19:00
  • This has problems in a ViewHolder. Seems like onGlobalLayout is not called if the item containing it is not currently in the RecyclerView. – Gagege Mar 29 '17 at 20:54
  • What attributes need to be set (or not set) on the ImageView in the xml for this to work? – Jan K May 02 '17 at 15:47
  • @JanK you can set whatever you like for the size, no attributes are really needed. Typically I suppose you'd want either `layout_width="match_parent"` or `layout_height="match_parent"`, and for that you need the latter example. – George Hilliard May 02 '17 at 16:03
91

I came across the same issue and it took me a while to track down a solution but I finally came across something that works for me.

Firstly I changed the Picasso call to

Picasso.with(this.context).load(message_pic_url)
.placeholder(R.drawable.profile_wall_picture)
.into(holder.message_picture);

Removing the fit and the centerInside. Next you need to add the following lines to the ImageView in your XML

android:scaleType="fitStart"
android:adjustViewBounds="true"

Hopefully it will work for you as well.

Cassie
  • 5,223
  • 3
  • 22
  • 34
64

Finally I solved it doing a transformation of Picasso, here is the snippet:

    Transformation transformation = new Transformation() {

        @Override
        public Bitmap transform(Bitmap source) {
            int targetWidth = holder.message_picture.getWidth();

            double aspectRatio = (double) source.getHeight() / (double) source.getWidth();
            int targetHeight = (int) (targetWidth * aspectRatio);
            Bitmap result = Bitmap.createScaledBitmap(source, targetWidth, targetHeight, false);
            if (result != source) {
                // Same bitmap is returned if sizes are the same
                source.recycle();
            }
            return result;
        }

        @Override
        public String key() {
            return "transformation" + " desiredWidth";
        }
    };

    mMessage_pic_url = message_pic_url;

    Picasso.with(this.context)
        .load(message_pic_url)
        .error(android.R.drawable.stat_notify_error)
        .transform(transformation)
        .into(holder.message_picture, new Callback() {
            @Override
            public void onSuccess() {
                holder.progressBar_picture.setVisibility(View.GONE);
            }

            @Override
            public void onError() {
                Log.e(LOGTAG, "error");
                holder.progressBar_picture.setVisibility(View.GONE);
            }
    });

This line is for customize with your desired width:

int targetWidth = holder.message_picture.getWidth();

Additionally this snipped include Callback for loading hide and error drawable built-in Picasso.

If you need more information to debug any error, you MUST implement a custom listener (Picasso builder) beacuse the onError Callback information is "null". You only know that there is an error for UI behavior.

I hope this helps someone to save many hours.

I'm a frog dragon
  • 7,865
  • 7
  • 46
  • 49
wendigo
  • 1,973
  • 2
  • 17
  • 21
12

May Accepted Answer is Useful to all but If you are binding Multiple ViewHolder for Multiple Views then you can reduce your code by Creating Class for Transformation and passing ImageView from ViewHolder.

/**
 * Created by Pratik Butani
 */
public class ImageTransformation {

    public static Transformation getTransformation(final ImageView imageView) {
        return new Transformation() {

            @Override
            public Bitmap transform(Bitmap source) {
                int targetWidth = imageView.getWidth();

                double aspectRatio = (double) source.getHeight() / (double) source.getWidth();
                int targetHeight = (int) (targetWidth * aspectRatio);
                Bitmap result = Bitmap.createScaledBitmap(source, targetWidth, targetHeight, false);
                if (result != source) {
                    // Same bitmap is returned if sizes are the same
                    source.recycle();
                }
                return result;
            }

            @Override
            public String key() {
                return "transformation" + " desiredWidth";
            }
        };
    }
}

Calling from ViewHolder:

Picasso.with(context).load(baseUrlForImage)
                     .transform(ImageTransformation.getTransformation(holder.ImageView1))
                     .error(R.drawable.ic_place_holder_circle)
                     .placeholder(R.drawable.ic_place_holder_circle)
                     .into(holder.mMainPhotoImageView1);

Hope it will Help you.

Community
  • 1
  • 1
Pratik Butani
  • 60,504
  • 58
  • 273
  • 437
  • Thanks, great solution for ImageView, one question: Can we do this for VideoView size same as ImageView passed in parameter ? – Vrajesh Oct 17 '17 at 10:55
  • @Pratik i have recyclerview and when scroll fast, i got exception:Transformation transformation desiredWidth crashed with exception. Caused by: java.lang.IllegalArgumentException: width and height must be > 0 – Vrajesh Oct 17 '17 at 11:45
  • While scrolling, sometimes `imageView.getWidth()` returns 0 and it causes an error `width and height must be > 0`. Any ideas how to fix this error? – Shahood ul Hassan Oct 13 '19 at 09:45
  • In that case, may be your image url is null so check it first if null or not. – Pratik Butani Oct 14 '19 at 04:46
5
    Picasso.with(this).load(url).resize(1800, 1800).centerInside().into(secondImageView)

    <ImageView
        android:id="@+id/SecondImage"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentLeft="true"
        android:adjustViewBounds="true"
        android:layout_margin="10dp"
        android:visibility="gone"/>

This will help you with variable height of images for all devices

Narik
  • 166
  • 1
  • 7
2
imageView.post(new Runnable() {
      @Override public void run() {
        Picasso.with(context)
            .resize(0, imageView.getHeight())
            .onlyScaleDown()
            .into(imageView, new ImageCallback(callback, null));
      }
    });
Ihor Bykov
  • 1,843
  • 3
  • 15
  • 22
1

I've wrote simple helper that take care about adding layout complete listener and call into(imageView) when layout process complete.

public class PicassoDelegate {

private RequestCreator mRequestCreator;

public PicassoDelegate(ImageView target, RequestCreator requestCreator) {
    if (target.getWidth() > 0 && target.getHeight() > 0) {
        complete(target, requestCreator);
    } else {
        mRequestCreator = requestCreator;
        target.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                v.removeOnLayoutChangeListener(this);
                complete((ImageView) v, mRequestCreator);
            }
        });

    }

}

private void complete(ImageView target, RequestCreator requestCreator) {
    if (target.getWidth() > 0 && target.getHeight() > 0) {
        requestCreator.resize(target.getWidth(), target.getHeight());
    }

    requestCreator.into(target);
}

}

So you can easily use it like this for example in fragment's onViewCreated()

new PicassoDelegate(customerPhoto, Picasso.with(getActivity()).load(user.getPhotoUrl()).centerCrop());
Dmytro
  • 823
  • 11
  • 14
1
public class CropSquareTransformation implements Transformation {

  private int mWidth;
  private int mHeight;

  @Override public Bitmap transform(Bitmap source) {
    int size = Math.min(source.getWidth(), source.getHeight());

    mWidth = (source.getWidth() - size) / 2;
    mHeight = (source.getHeight() - size) / 2;

    Bitmap bitmap = Bitmap.createBitmap(source, mWidth, mHeight, size, size);
    if (bitmap != source) {
      source.recycle();
    }

    return bitmap;
  }

  @Override public String key() {
    return "CropSquareTransformation(width=" + mWidth + ", height=" + mHeight + ")";
  }

More transformations: https://github.com/wasabeef/picasso-transformations

Pablo Cegarra
  • 20,955
  • 12
  • 92
  • 110
1

extend ImageView then override onMeasure method like the following.

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        Drawable d = getDrawable();

        if(d!=null && fittingType == FittingTypeEnum.FIT_TO_WIDTH){
            int width = MeasureSpec.getSize(widthMeasureSpec);
            int height = (int) Math.ceil((float) width * (float) d.getIntrinsicHeight() / (float) d.getIntrinsicWidth());
            setMeasuredDimension(width, height);
        }else{
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
Jinichi
  • 51
  • 1
  • 1
  • 10
1

Actually i was getting in while loading image in CustomImageView which had zoomable functionality

Error was

java.lang.RuntimeException: Transformation transformation desiredWidth crashed with exception.

I solved it by editing code given from accepted answer i got the max width of my display as if my imageview width was already match_parent.

if (!imgUrl.equals("")) {

        DisplayMetrics displayMetrics = new DisplayMetrics();
        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        int height = displayMetrics.heightPixels;
        int width = displayMetrics.widthPixels;

        Picasso.with(context).load(imgUrl)
                .transform(getTransformation(width, imageView))
                .into(imageView, new Callback() {
                    @Override
                    public void onSuccess() {
                        if (progressBar != null) {
                            progressBar.setVisibility(View.GONE);
                        }
                    }

                    @Override
                    public void onError() {
                        if (progressBar != null) {
                            progressBar.setVisibility(View.GONE);
                        }
                    }
                });
    }

    public static Transformation getTransformation(final int width, final ImageView imageView) {
        return new Transformation() {
            @Override
            public Bitmap transform(Bitmap source) {
                int targetWidth = width;
                double aspectRatio = (double) source.getHeight() / (double) source.getWidth();
                int targetHeight = (int) (targetWidth * aspectRatio);
                Bitmap result = Bitmap.createScaledBitmap(source, targetWidth, targetHeight, false);
                if (result != source) {
                    // Same bitmap is returned if sizes are the same
                    source.recycle();
                }
                return result;
            }

            @Override
            public String key() {
                return "transformation" + " desiredWidth";
            }
        };
    }
Vivek Barai
  • 1,338
  • 13
  • 26
1
Picasso.get()
.load(message_pic_url)
.fit()
.centerCrop()
.placeholder(R.drawable.profile_wall_picture)
.into(holder.message_picture);

Try this code, Worked for me.

Afinas EM
  • 2,755
  • 1
  • 15
  • 28
1
@Override
    protected void onResume() {
        super.onResume();

        imageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                loadImageIfReady();
            }
        });

    }

    private void loadImageIfReady() {
        if (imageView.getMeasuredWidth() <= 0 || mPayload == null)
            this.finish();    // if not ready GTFO

        Picasso.with(this)
                    .load(mPayload)
                    .resize(imageView.getMeasuredWidth(), imageView.getMeasuredWidth())
                    .centerInside()
                    .into(imageView);


    }
Avinash
  • 11
  • 2
1

I think the value fitXY will work for you, here is an example that worked for me :

    <ImageView
        android:id="@+id/imageView_answer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:scaleType="fitXY"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:contentDescription="@string/answer_image" />