best practice to picasso fetch with match parent in android - android

I'm using Picasso to download pictures and show them in a RecyclerView. I'm trying to prevent the small delay which is happening when I'm scrolling the RecyclerView and Picasso is downloading the new pictures, so I'm using fetch. When I was using exact size for the pictures, it worked well, but now I'm using "match parent" (to work on all devices) in my xml, and since then there is a delay and when I'm scrolling back and forth, it doesn't remember the pictures (delay on the pictures that has been already seen).
I tried just fit() and centerCrop() but it was slow, so this is my current try of solving the issue:
in the adapter (this runs only once) I set a listener to get the exact size of the ImageViews
private void preloadImage(final View picture) {
ViewTreeObserver observer = picture.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
PictureUtil.fetchImage(picture.getHeight(), picture.getWidth());
ViewTreeObserver mObserver = picture.getViewTreeObserver();
mObserver.removeOnGlobalLayoutListener(this);
}
});
}
In the PictureUtil I fetch all the pictures:
public static void fetchImage(int height, int width) {
if (height > 0 && width > 0) {
for (House house : Houses.houseList) {
Picasso.get().load(getFrontPicturePath(house.id))
.resize(width, height)
.centerCrop()
.fetch();
}
}
}
and in the adapter again this is how I load them:
Picasso.get()
.load(PictureUtil.getFrontPicturePath(houseId))
.error(PictureUtil.getPlaceholderPath(context))
.fit()
.centerCrop()
.into(view);
and this is the imageview in the xml:
<ImageView
android:id="#+id/house_row_picture"
android:layout_width="match_parent"
android:layout_height="170dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="#drawable/img_small" />
So the problem again that I got delay on the images (not like when I had fix sizes) and when I'm scrolling in the Recyclerview the images disappear and load again. What am I doing wrong? Is there a best practice for this?
Thanks in advance

Related

Can't resize picture in CircleImageView

I have this XML:
<de.hdodenhof.circleimageview.CircleImageView
android:src="#drawable/ic_kitty_superhero"
android:id="#+id/userAvatar"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_gravity="center"
android:layout_marginBottom="16dp"
app:civ_border_color="#android:color/white" // just to show you wrong result
app:civ_border_width="1dp" />
and this extension function to load image:
fun ImageView.loadImage(context: Context, url: String) {
val resourceID = context.resources.getIdentifier(url, "drawable", context.packageName)
if (resourceID != 0) { // if it's an image from drawable folder
Glide.with(context)
.load(resourceID)
.fitCenter()
.into(this)
} else {
Picasso.with(context)
.load(url)
.fit()
.centerInside()
.into(this)
}
}
Since Picasso doesn't know how to work with resourceID, I need to use Glide or something similar like this.setImageResource(). But it's cannot execute transformation that I want.
In the first photo, everything looks the way I want, but it’s just used ImageView container.
In the second photo I use CircleImageView container and it no longer looks the way I would like - the ears are cropped in the photo.
Please, can you help me, how can I get the correct result?
NOTE: I can't use .load(R.drawable.icon) and I can't use the ImageView container (I can probably if make it circle somehow)
I solved my problem by using ImageView container and Circle Transformation for Picasso

Google Ads MediaView not correctly resizing height to wrap_content when displaying image

I got an email from AdMob today saying:
Change to native ads policy: Native ads will require MediaView to
render the video or main image asset. In an effort to help you deliver
a better ad experience more easily, beginning October 29th, native ads
will require MediaView to render the video or main image asset. Ad
units not compliant by this date will stop serving ads, which could
impact your ad revenue.
I tried this out in my Android app, removing the separate handling of images with ImageView and video with MediaView, but I have found that the MediaView is not resizing the view's height according to the height of the image it displays.
In this codelab example from Google, a fixed height and width for the MediaView are used. I cannot do this, as this screen is responsive to the screen size, which will change depending on the device. The fact that the image can be dynamically resized is one of the main benefits for using UnifiedNativeAds instead of predefined ads such as banners.
This is how I need to be displaying the MediaView, using match_parent for width and wrap_content for height.
<com.google.android.gms.ads.formats.MediaView
android:id="#+id/ad_media"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"
android:scaleType="fitXY"/>
This is what I am currently getting from the above code
This is what I need and expect it to look like from using wrap_content
In the previous case where we were able to render the images separately using ImageView, the wrap_content value correctly sized the image.
Does anyone have a workaround for this? How can I follow the new Google requirements without hardcoding the MediaView's height?
My full code can be found here, in my demo app on github.
mediaView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
#Override
public void onChildViewAdded(View parent, View child) {
if (child instanceof ImageView) {
ImageView imageView = (ImageView) child;
imageView.setAdjustViewBounds(true);
}
}
#Override
public void onChildViewRemoved(View parent, View child) {}
});
I was facing the same problem, after trying a lot with trial and error, I found that wrapping <FrameLayout...>(in which you are adding UnifiedNativeAdView) with ScrollView will resolve this problem.
If implementing Advanced Native Ads, use "imageScaleType" property of MediaView as suggested here in the official docs https://developers.google.com/admob/android/native/advanced#setting_imagescaletype
adView.mediaView.setImageScaleType(ImageView.ScaleType.CENTER_CROP)
or any other ScaleType as per requirement.
To ensure that all media are as wide as possible and that they respect a maximum height (so there are no surprises depending on the dimensions of the media).
XML
<com.google.android.gms.ads.formats.MediaView
android:id="#+id/ad_media"
android:layout_gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
JAVA
mediaView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
#Override
public void onChildViewAdded(View parent, View child) {
float scale = context.getResources().getDisplayMetrics().density;
int maxHeightPixels = 175;
int maxHeightDp = (int) (maxHeightPixels * scale + 0.5f);
if (child instanceof ImageView) { //Images
ImageView imageView = (ImageView) child;
imageView.setAdjustViewBounds(true);
imageView.setMaxHeight(maxHeightDp);
} else { //Videos
ViewGroup.LayoutParams params = child.getLayoutParams();
params.height = maxHeightDp;
child.setLayoutParams(params);
}
}
#Override
public void onChildViewRemoved(View parent, View child) {}
});
for (int i = 0; i < mediaView.getChildCount(); i++) {
View view = mediaView.getChildAt(i);
if (view instanceof ImageView) {
((ImageView) view).setAdjustViewBounds(true);
}
}
This works for me.I tried Richard's answer,But it didn't works well in a RecyclerView.

Measuring an ImageView after loading an image

Im trying to find a way to measure an ImageView after an image has been loaded into it using Glide or Picasso (or anything really). Basically, im trying to layout other views on top of the image in certain positions, but need the final ImageViews dimensions to do so accurately.
Im not sure what the best layout to use to try and do this would be, but im currently using this:
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/viewingImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>
and loading the image into viewingImageView. These are both inside a root FrameLayout as well but I dont think that matters.
This is my latest attempt, but as commented, using .getWidth() and .getHeight() on the ImageView return 0. The width/height of the resource returns the size of the original image.
Glide
.with(this)
.load(entry.getImageUrl())
.asBitmap()
.into(new SimpleTarget<Bitmap>() {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
mImageView.setImageBitmap(resource);
int width = mImageView.getMaxWidth(); //prints 0
int height = mImageView.getMaxHeight(); //prints 0
int resw = resource.getWidth(); //returns original image width
}
});
So how can I get the image loaded and then measure the ImageView (or its wrapping FrameLayout) after the image has been loaded? Or if possible, measuring the dimensions of the finally laid out image would be better, as I know the image doesnt always fill the entire ImageView depending on scale types. I'm open to any solutions, the above is only what ive tried so far.
You should wait for the view to be drawn, you can use OnGlobalLayoutListener like this
ViewTreeObserver vto = mImageView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
this.mImageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
// Get the width and height
int width = mImageView.getMeasuredWidth();
int height = mImageView.getMeasuredHeight();
}
});
Create a custom class with image view.
Use this onMeasure in the image view we can get the height and width
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mNeedsInitialScale) { // Set by setImage when a new image is set
// The measured width and height were set by super.onMeasure above
setInitialScale(getMeasuredWidth(), getMeasuredHeight());
mNeedsInitialScale = false;
}
}

Why images loaded by Glide get downsampled to low resolution in a RecyclerView?

I'm switching from Picasso to Glide in my app. I have a RecyclerView, each of whose items is a CustomView. This CustomView adds TextView or ImageView dynamically based on different data sources.
A FillWidthImageView is used to display these images, it fill the screen width automatically. When I use Picasso, it works well. However, when I use Glide to load images, the image displayed in the FillWidthImageView looks like a mosaic with extremely low resolution, because it's heavily downsampled.
And if I use a normal ImageView instead, size of loaded image is the same as the placeholder, and images still look like a mosaic. If there is no placeholder, the images look tiny on the screen. And if I display the images in an empty Activity, they will be loaded with full resolution and correct size.
Code of adding ImageView in my CustomView(extends LinearLayout), called in RecyclerView.onCreateViewHolder()
public void addImageView() {
ImageView imageView = new FillWidthImageView(getContext());
imageView.setPadding(0, 0, 0, 10);
imageView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
addView(imageView);
}
Code of FillWidthImageView
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable drawable = getDrawable();
if (drawable != null) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int diw = drawable.getIntrinsicWidth();
if (diw > 0) {
int height = width * drawable.getIntrinsicHeight() / diw;
setMeasuredDimension(width, height);
} else
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Code of loading images, called in RecyclerView.onBindItemViewHolder()
Glide.with(getContext()).load(url).into(imageView);
Thanks #Hitesh Kr Sahu, but I have solved of the bug by myself. It's because the Glide didn't get the right dimensions of my CustomView wrapper. I solved this bug by applying the dimensions by calling .override(int,int) and .fitCenter(). I passed the width and height of screen to it, so it will be scaled fitting the screen size.
Image loaded by Glide has the worse quality compared to Picasso because Glide's default Bitmap Format is set to RGB_565 & it consumed just 50% memory footprint compared to ARGB_8888.
That make Glide faster than Picasso as you can see in this video.
Good news is you can switch Bitmap Format to ARGB_8888 by creating a new class which extended from GlideModule like this :-
public class GlideConfiguration implements GlideModule {
#Override
public void applyOptions(Context context, GlideBuilder builder) {
// Apply options to the builder here.
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
}
#Override
public void registerComponents(Context context, Glide glide) {
// register ModelLoaders here.
}
}
and adding metadata field in manifest
<meta-data android:name="com.inthecheesefactory.lab.glidepicasso.GlideConfiguration"
android:value="GlideModule"/>
You can read more about it here glide Configuration page of Glide's wiki and as you have switched from Picasso you will find this an interesting read

How can I improve the performance (first load time, scroll smoothness) of a GridView rendering many large images?

My application allows users to take photos with their devices camera app, which are saved to an app-specific folder.
Then, users can choose to send any image they choose to one of their contacts, using their choice of their devices email app.
Sadly the "choose images to send" dialog takes a long time to load initially, and is very "choppy" (frequent noticable pauses) when scrolling. I suspect that this is because the images are very large — pictures taken with my camera are around 2.3MB each. The initial screen (15 images displayed) thus needs to pull around 34.5MB off disk before it can render.
GridView layout.xml
<!-- ... -->
<GridView
android:id="#+id/gvSelectImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:numColumns="3"
android:gravity="center" />
<!-- ... -->
Adapter getView()
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
File pic = pics[position];
View checkedImageView =
LayoutInflater.from(context).inflate(R.layout.checked_image,
parent, false);
final ImageView imageView =
(ImageView) checkedImageView.findViewById(R.id.checkableImage);
/* LOAD IMAGE TO DISPLAY */
//imageView.setImageURI(pic.toURI())); // "exceeds VM budget"
Bitmap image;
try {
int imageWidth = 100;
image = getBitmapFromFile(pic, imageWidth); // TODO: slooow...
} catch (IOException e) {
throw new RuntimeException(e);
}
imageView.setImageBitmap(image);
/* SET CHECKBOX STATE */
final CheckBox checkBoxView =
(CheckBox) checkedImageView.findViewById(R.id.checkBox);
checkBoxView.setChecked(isChecked[position]);
/* ATTACH HANDLERS */
checkBoxView.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
CheckableLocalImageAdapter.this.isChecked[position] =
checkBoxView.isChecked();
}
});
imageView.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
checkBoxView.setChecked(!checkBoxView.isChecked());
}
});
return checkedImageView;
}
getBitmapFromFile() is just adapted from the answer given at https://stackoverflow.com/a/823966/633626. I note that the code there calls decodeStream() twice and might be doubling my I/O, but I think the dialog will still be too slow and too jerky even if I halve the amount of time to takes to render.
My testing phone is a standard Samsung Galaxy S II if that's relevant. I suspect older devices will be even slower / choppier.
What's the best way to improve performance here? I'm guessing a combination of an AsyncTask to load each image (so that the dialog can load before all images are rendered) and some sort of caching (so that scrolling doesn't require rerendering images and isn't jerky), but I'm lost as to how to fit those pieces in with the rest of my puzzle.
You'll want to work with thumbnails instead. Loading the whole image from the SD card will always be slow. Only load the full version of the one(s) they really want to send.
This isn't to say that caching and async loading are a bad idea. I'd change the code to work with thumbnails first, recheck performance, and then go the async route. You can show a little spinner over the currently loading thumbnail to indicate that there's more to come.
Here's a built-in class to help you retrieve them.
Try loading your Images with a SampleSize into the GridView:
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 7;
Bitmap bitmap = BitmapFactory.decodeFile(f.getPath(), opts);
More Info about the Options here
And also you can swap the image loading into a Thread or AsyncTask.

Categories

Resources