I have a RecyclerView with CardViews inside a staggered grid layout. Each CardView has a custom adjustable height ImageView inside. I am using Picasso to load images into the ImageView.
I am trying to first transform the image to match the width of the ImageView and set the height so that aspect ratio is maintained. I am then trying to resize the height of ImageView to match the image.
Everything works on the first load but when I scroll down my custom transformation isn't able to get the width of the ImageView it just returns 0 and this causes the app to crash.
I tried with a getViewTreeObserver like suggested in this post but then only the first four pictures load, the next few images are blank, and then the first four images repeat themselves in the wrong CardViews.
Here is the code I have that loads properly but crashes on scroll:
Recycler Adapter
public class PlacesRecyclerAdapter extends
RecyclerView.Adapter<PlacesRecyclerAdapter.PlacesView> {
...
@Override
public void onBindViewHolder(final PlacesView placesView, final int i) {
Target target = new Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
float ratio = (float) bitmap.getHeight() / (float) bitmap.getWidth();
placesView.placeImage.setAspectRatio(ratio);
placesView.placeImage.setImageBitmap(bitmap);
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
placesView.placeImage.setTag(target);
// Setup details variables
final Business place = mPlaces.get(i);
String photo = place.getImage_url();
// Load Image with Picasso
Picasso.with(mContext)
.load(photo)
.transform(new FitToViewTransformation(placesView.placeImage))
.into(target);
}
...
}
Custom Transformation
public class FitToViewTransformation implements Transformation {
private DynamicHeightImageView mView;
public FitToViewTransformation(DynamicHeightImageView view) {
mView = view;
}
@Override
public Bitmap transform(Bitmap source) {
int targetWidth = mView.getWidth(); // This returns the width on the first page load but on scrolling returns 0
Log.v("FitToView:", "Target width: " + targetWidth);
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";
}
}
Update 1 (failure)
I went ahead and tried the OnGlobalLayoutListener again and I still get just the first four images then four blank images, then the first four showing again. This is how I implemented it in my onBindViewHolder:
placesView.placeImage.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
placesView.placeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Picasso.with(mContext)
.load(photo)
.placeholder(R.drawable.cast_album_art_placeholder)
.error(R.drawable.item_icon_parking)
.transform(new FitToViewTransformation(placesView.placeImage))
.into(target);
}
});
Update 2 (success... I guess)
So I have found a fix, although I'm not so sure it is the right thing to do. It does work though and the app doesn't have any unwanted behavior. All I did was add an empty while loop to the beginning of my transform method like this:
@Override
public Bitmap transform(Bitmap source) {
double aspectRatio = (double) source.getHeight() / (double) source.getWidth();
while(mView.getWidth() == 0) {
}
int targetWidth = mView.getWidth();
Log.v("FitToView:", "Target width: " + targetWidth);
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;
}