I have a very simple transition between two activities, and sometimes, the image stops in middle, lags, flashes a black color.
I have used shared-element-transitions many times with Glide, but I can't make it not lag this time.
this is the first Activity:
val intent = Intent(this, MediaZoomImageActivity::class.java)
intent.putExtra(MediaZoomActivity.ZOOM_MEDIA_URL, submission.imageUrl)
val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(this,
post_image_parallax, ViewCompat.getTransitionName(post_image_parallax)).toBundle()
startActivity(intent, bundle)
this is the second Activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_zoom_image)
postponeEnterTransition()
back.setOnClickListener { finish() }
val url = intent.getStringExtra(ZOOM_MEDIA_URL)
Glide.with(this)
.load(url)
.apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.RESOURCE))
.into(media_zoom_image)
media_zoom_image.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
media_zoom_image.viewTreeObserver.removeOnPreDrawListener(this)
startPostponedEnterTransition()
return true
}
})
this is the layout for the second Activity:
constraintLayout
-backbutton (ImageButton)
-image (ImageView)
this is the animation:
<changeBounds
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200" />
It's very simple, but I can't wrap my head around it.
The second image originally had some listener to use gestures, and I thought that was the reason, but commenting everything out still it does that lag.
The image in the first Activity is the target of another shared-element-transition with the same transitionName, can that be the problem?
Am I doing something wrong? Is it a problem with ConstraintLayout? Is there a problem with Glide?
Thanks in advance for the help.
Instead using PreDrawListener, using listener with glide would fix it. Looks like there is a timing issue
Glide
.with(this)
.asBitmap()
.load(url)
.apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.RESOURCE))
.listener(new RequestListener<Bitmap>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
media_zoom_image.setImageBitmap(resource);
startPostponedEnterTransition();
return true;
}
})
.into(media_zoom_image);
Make sure you remove
media_zoom_image.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
media_zoom_image.viewTreeObserver.removeOnPreDrawListener(this)
startPostponedEnterTransition()
return true
}
})
UPDATE
I have updated onResourceReady block. Give it a try please.
Glide
.with(this)
.asBitmap()
.load(url)
.apply(RequestOptions().diskCacheStrategy(DiskCacheStrategy.RESOURCE))
.listener(new RequestListener<Bitmap>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
startPostponedEnterTransition();
return false;
}
})
.into(media_zoom_image);
UPDATE 2
Call postponeEnterTransition as early as possible.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
postponeEnterTransition(); // Called it before set content. Try to call it before super.onCreate to see if it also works?
setContentView(R.layout.activity_zoom_image);
...
})
UPDATE 3
Optimize your images. There are some online websites or tools for it, or you can ask your graphic designer.
Also you can increase duration of animation to give time for smooth loading.
<changeBounds
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300" />
Notice that the listener in glide, runs before setting bitmap to view. Difference between Picasso and Glide, Picasso has callback parameter in into() method as optional, whichs runs onSuccess() after setting bitmap to view. So the execution which makes our shared transition is choppy (ImageView#setBitmapImage) already ran while postponed. Picasso has shining little bit more in this situation.
Related
I'm trying to load an image using Glide.
I gave it a url however it results in a FileNotFound exception.
However this is not my concern, my concern is that I want to load a placeholder in case any of these errors occur.
I followed the guide to use glide, but nothing occurs.
Please take a look at my code.
I tried adding RequestListener and error fallback.
Glide.with(imageView.getContext()).load(url)
.transition(DrawableTransitionOptions.withCrossFade())
.apply(new RequestOptions().placeholder(placeholder))
.error(Glide.with(imageView.getContext()).load(placeholder))
.listener(new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
})
.into(imageView);
I want my placeholder to be loading in my imageview.
Actual result is that my imageview is loaded with some placeholder not defined by me, something indicating that the image link is forbidden or something.
Expected: I want my placeholder to be loaded.
Try this:
int placeholder = R.drawable.error
GlideApp.with(imageView.getContext())
.load(url)
.apply(new RequestOptions().placeholder(placeholder)
.error(placeholder).dontTransform()
.diskCacheStrategy(DiskCacheStrategy.ALL))
.into(mImageView);
You can refer code below to show placeholder image in case of Error :
Glide.with(context).load(BASE_URL + imageUrl)
.apply(new RequestOptions().placeholder(R.drawable.defaultimage)
.error(R.drawable.defaultimage)).into(imageView);
Here is modified code:
int placeholder = R.drawable.placeholder
Glide.with(imageView.getContext())
.load(url)
.error(placeholder)
.placeholder(placeholder)
.into(imageView);
Try with this code below.
Glide.with(your context).load(imageurl).apply(RequestOptions.placeholderOf(your drawable here)).error(your error drawable).into(your imageview);
Hope it works...
Try using this-
.error(getResourses.getDrawable(R.drawable.placeholder))
instead of using this-
.error(Glide.with(imageView.getContext()).load(placeholder))
Try using RequestOptions
RequestOptions requestOptions = new RequestOptions();
requestOptions.placeholder(R.drawable.ic_placeholder);
requestOptions.error(R.drawable.ic_error);
Glide.with(context)
.load(url)
.apply(requestOptions)
.into(holder.imageView);
I am loading images using Glide 4.1.1 with disk caching and it's working perfectly on other devices than Red Mi note 4. Red Mi note 4 not showing any cached image. while my emulator and Micromax android one are showing cached images perfectly.
Glide.with(context)
.load(stringUrl).apply(new RequestOptions().diskCacheStrategy(DiskCacheStrategy.RESOURCE))
.listener(new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
viewHolderListener.onLoadCompleted(image, adapterPosition);
return false;
}
#Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
L.d(loadedData.get(adapterPosition).getImagePath());
viewHolderListener.onLoadCompleted(image, adapterPosition);
return false;
}
})
.into(image);
Try and update the glide library to new version
dependencies {
implementation ("com.github.bumptech.glide:glide:4.9.0") {
exclude group: "com.android.support"
}
Most important you have to include AppGlide module in your application
package com.example.myapp;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
#GlideModule
public final class MyAppGlideModule extends AppGlideModule {}
Finally you can enable caching by writing below code:
GlideApp.with(fragment)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView);
Refer: GlideV4 documentation
I want to get the bitmap from an image using Glide. I am doing the following -
Bitmap chefBitmap = Glide.with(MyActivity.this)
.load(chef_image)
.asBitmap()
.into(100, 100)
.get();
It used to work with the previous Glide version.
But it does not work with this in gradle - "compile 'com.github.bumptech.glide:glide:4.0.0'"
I want to use this dependency because this is the latest version.
Can anyone help me in this regard. Thanks in advance.
Bitmap chefBitmap = Glide.with(MyActivity.this)
.asBitmap()
.load(chef_image)
.submit()
.get();
There is little changes according to latest version of Glide. Now we need to use submit() to load image as bitmap, if you do not call submit() than listener won't be called. In 4.0 Version, submit() added and in order to invoke listener. One of user commented code is working with GlideApp. You can use below code to run with GlideApp if you are using.
here is working example i used today.
Glide.with(cxt)
.asBitmap().load(imageUrl)
.listener(new RequestListener<Bitmap>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object o, Target<Bitmap> target, boolean b) {
Toast.makeText(cxt,getResources().getString(R.string.unexpected_error_occurred_try_again),Toast.LENGTH_SHORT).show();
return false;
}
#Override
public boolean onResourceReady(Bitmap bitmap, Object o, Target<Bitmap> target, DataSource dataSource, boolean b) {
zoomImage.setImage(ImageSource.bitmap(bitmap));
return false;
}
}
).submit();
It is working and i m getting bitmap from listener.
You need to set the size with RequestOptions in apply() and use a RequestListener to retrieve the bitmap. The mthod asBitmap() needs to be called before load(). So it will look like this:
Glide.with(getContext().getApplicationContext())
.asBitmap()
.load(chef_image)
.apply(new RequestOptions().override(100, 100))
.listener(new RequestListener<Bitmap>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
// resource is your loaded Bitmap
return true;
}
});
You should add in your
dependencies{
compile 'com.github.bumptech.glide:glide:4.0.0'
compile 'com.android.support:support-v4:25.3.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.0.0'
Also, give permission in your manifest.xml
Try this in your build.gradle;
compile 'com.github.bumptech.glide:glide:3.7.0'
and load your bitmap like below;
Glide.with(activity).load(m.getThumbnailUrl())
.thumbnail(0.5f)
.crossFade()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageview);
I know it isn't very practical to load bitmaps from the device storage synchronously, but I really have to do it. I haven't figured out any way to do this.
Yes is possible and is in glide documentation.
For example if you need to retrive the Bitmap synchronously you can do:
Glide V3:
Bitmap myBitmap = Glide.with(applicationContext)
.load(yourUrl)
.asBitmap()
.into(500, 500)
.get()
Glide v4:
FutureTarget<Bitmap> futureBitmap = Glide.with(applicationContext)
.asBitmap()
.load(yourURL))
.submit();
Bitmap myBitmap = futureBitmap.get();
Note: This code need to be run in the background or the app will crash.
Using interfaces to load data after image is loaded is a better option.
Create a new file called OnglideLoaded.java file like this:
import android.graphics.drawable.Drawable;
public interface OnGlideLoaded {
void onGlideLoaded(Drawable drawable);
}
Now create setter method for it like this where glide is:
public void setOnGlideLoaded(OnGlideLoaded onGlideLoaded) {
this.onGlideLoaded = onGlideLoaded;
}
Not call it inside Glide Target:
Glide.with(context)
.asBitmap()
.load(uri)
.into(new CustomTarget<Bitmap>() {
#Override
public void onResourceReady(#NonNull Bitmap resource, #Nullable Transition<? super Bitmap> transition) {
icon = resource;
if (onGlideLoaded!=null){
//This will send the image to every registered interface
onGlideLoaded.onGlideLoaded(icon);
}
}
#Override
public void onLoadCleared(#Nullable Drawable placeholder) {
}
});
Now call the interface like this where you want the image:
reference.setOnGlideLoaded(new OnGlideLoaded() {
#Override
public void onGlideLoaded(Drawable drawable) {
imageView.setImageBitmap(drawable);
}
});
Here, reference will be the object where you created the setter method.
Im having big troubles using a Target inside an adapter. Im confused about the documentation on the code
Objects implementing this class must have a working implementation of
{#link #equals(Object)} and {#link #hashCode()} for proper storage internally. Instances of this
interface will also be compared to determine if view recycling is occurring. It is recommended
that you add this interface directly on to a custom view type when using in an adapter to ensure
correct recycling behavior.
Im trying to use the Target in this way:
class CustomTarget implements Target {
private ImageView imageView;
public CustomTarget(ImageView imageView) {
this.imageView = imageView;
}
#Override
public void onBitmapLoaded(final Bitmap bitmap, Picasso.LoadedFrom from) {
imageView.setImageDrawable(new RoundedAvatarDrawable(bitmap));
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
imageView.setImageDrawable(errorDrawable);
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
imageView.setImageDrawable(placeHolderDrawable);
}
#Override
public boolean equals(Object o) {
return imageView.equals(o);
}
#Override
public int hashCode() {
return imageView.hashCode();
}
}
#Override
public View getView(int position, View v, ViewGroup parent) {
....
RoundedAvatarDrawable r = new RoundedAvatarDrawable(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_avatar_seahorse));
ImageCacheController.with(mContext).getPicasso().load(member.getPicture_url()).resize(100, 100).centerCrop().placeholder(r).error(r).into(new CustomTarget(viewHolder.ivAvatar));
....
}
It's doesn't work and the images change between each others randomly
You don't show your whole getView function, so without knowing how you use the viewHandler, here's my take on what's going on:
Your problem is that you're creating a new CustomTarget every time getView gets called. You are going against the point of having a Target object. Let me elaborate.
When a new download request is made, previous requests to the same target get stopped or don't result in a call to the Target's callbacks. (so if the Target gets reused for a different row in a list it doesn't get both rows' images).
You are using a new object for each request, effectively hinting Picasso that each request is for a different row so to speak. The doc says "Instances of this interface will also be compared to determine if view recycling is occurring", so since each request has a newly created CustomTarget object, no two requests will have the same object and a row recycle won't be detected.
You're also using viewHolder. In this case I think the viewHolder should be extending the Target interface (if you only have 1 image per row). This way everytime you request a download you can use the same object and not create a new one.
You're also delegating the implementation of your CustomTarget to the ImageView's implementation. Make sure that ImageView's equals and hashCode functions fullfill the requirements Picasso asks for.
Some info on how to implement equals and hashCode: What issues should be considered when overriding equals and hashCode in Java?
It seems your equals method is broken. You are comparing an imageview to a custom target. This might fix it:
public boolean equals(Object o) {
if(o instanceof CustomTarget) {
return ((CustomTarget) o).imageView.equals(this.imageView);
}
return super.equals(o);
}