I have a List View of images.
If no image exists in the local cache, the placeholder image will remain in place of the image.
Then an asynchronous task is run to download the image from a server.
Once the async task finishes I set the imageView bitmap to the downloaded image.
Then I call notifyDataSetChanged()
The problem is that I need to scroll down and scroll back up for each cell to show the new image that has been downloaded. It doesn't update the cells automatically.
So I have:
public class ImageLoadTask extends AsyncTask<Void, Void, Bitmap> {
private ImageView imageView;
public ImageLoadTask(ImageView imageView) {
this.imageView = imageView;
}
#Override
protected Bitmap doInBackground(Void... params) {
// download image from server and return
}
#Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
imageView.setImageBitmap(result)
notifyDataSetChanged()
}
}
and in my getView() method I have:
if(image exists in cache){
myImageView.setBitmap(image in Cache);
}else{
new ImageLoadTask(myImageView).execute()
}
So any images that were stored in the Cache work fine.
Any images that have to be downloaded first will not update correctly, unless you scroll down and back up again.
What is the problem?
I believe you are doing thigs which have been already done and are working rly fine.
I advise you to use library for loading images with listview. One of these is picasso Picasso Android library. This library provides downloading and caching, and can easily (with one line) display them to imageview. Also it provides possibility for placeholder image.
Picasso.with(context)
.load(url)
.resize(50, 50)
.centerCrop()
.into(imageView)
Here is another question on stackoveflow:
Using Picasso library with ListView
EDIT:
If you use picasso you will ALSO solve problem with images wrong displaying while scrolling. This happens if you are loading/downloading them in AsyncTask and you are not doing it correctly.
I was answering question about this problem few daays ago: Why are wrong images getting loaded in ListView occasionally?
Firstly, if you are using the ImageView object directly, then you don't need to call notifyDataSetChanged.
Secondly, what you need to ensure is that the ImageView object you are passing still exists for the same URL requested, i.e., as you must know that Android will re-use views and might be if in between you had scrolled the ImageView object for which you had requested for an URL would have changed to some other URL.
I have worked on a very similar scenario, where I had a separate design like Adapter -> ImageViewRequest(This class will be used to post request, and this class maintains a map of ImageView and URL. So for every request entry is done to the map, and for every response we check that the response for the URL is the current requested URL for the entry of that ImageView and then set accordingly) -> AsyncTask
And also ensure that while setting the bitmap you do that inside a runOnUIThread task to ensure that the image is properly loaded, otherwise you might get a crash saying you are setting in a wrong thread.
Hope that helps :)
Related
I developed a chat app. It uses RecyclerView to show chat info. It's exactly like facebook messenger.
My problem is, loading the image of user I'm chatting to takes time, and make the app slow when there's large number of chat messages.
I'm using picasso to load image.
Picasso.get()
.load("https://domain/images/profile_picture/" + otherImage)
.networkPolicy(NetworkPolicy.OFFLINE)
.transform(new CircleTransform())
.into(imageView, new Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError(Exception e) {
int placeHo;
if(otherGender.equals("female"))
placeHo = R.drawable.ic_female_color;
else
placeHo = R.drawable.ic_male_color;
Picasso.get()
.load("https://domain/images/profile_picture/" + otherImage)
.error(placeHo)
.transform(new CircleTransform())
.centerCrop(Gravity.TOP)
.into(imageView, new Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError(Exception e) {
Log.v("Picasso","Could not fetch image");
}
});
}
});
Is there any efficient way to show the image? as it's showing the same image(user profile picture).
I can think only one reason that your RecyclerView is lagging. Because your image size is large.
See, if your chat image is about 50-100dp then you should use the same resolution image. Perhaps you are loading original image.
AFAIK I use Glide over Picasso because Glide optimize the downloaded image, as ImageView size.
See Glide doc
Glide's primary focus is on making scrolling any kind of a list of
images as smooth and fast as possible, but Glide is also effective for
almost any case where you need to fetch, resize, and display a remote
image.
You don't need to worry about cache in Picasso and Glide both. Cache is enabled by default in Picasso, so if same image is queried again, then it will be picked from cache.
Solution 1 (using Piccaso)
Resize image as much you need.
Picasso
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.resize(100, 100) // resizes the image to these dimensions (in pixel). does not respect aspect ratio
.into(imageViewResize);
Solution 2 (Using Glide)
You need not to worry about anything, you are good to go if you use Glide.
keep it Static
Or You can set it at the time of initialisation
not changing it every time on viewholder
sorry for my english and format of writing
If you are using the same image in all the Views of the same viewType then load the image in onCreateViewHolder method of the adapter and not in onBindViewHolder.
The same View with the image already loaded will be recycled (reused) and not loaded again and again, the result will be for sure faster.
You have to postpone the first image retrieve procedure at when the RecyclerView.OnChildAttachStateChangeListener() event signals that a child is added. Obliviously the Event is triggered for each of firsts visible items. The most correct way to postpone something like this is to create an Handler and then send/enqueue a Handler.sendMessage(Message.obtain(..)) one for each visible objects) items. Inside the Handler you will receive that Message and there you can execute the Picasso action.
Then you have to decide if load other (the not yet visible items) images all at once or just prepare the picture only when it is required by the Recycler.
I am doing an API request to Imgur using Retrofit in order to retrieve about 40 URLs and then display them in a RecyclerView using Glide like such:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Glide
.with(context)
.load(list.get(position).getLink()) // Gets the URL in my list
.centerCrop()
.placeholder(R.mipmap.placeholder)
.crossFade()
.into(holder.imageView);
}
The request gets answered quickly, but most of my images stay as placeholders as some of them appear one by one later on.
What is causing this delay in the display of my images? Is it perhaps linked to the speed of my Internet connection?
Additionally is my approach a correct one when it comes to "large" amounts of pictures?
Please note that most ImageViews do not load, even the ones that are visible to my user.
This might be depending on your internet connection. If so, you can use imgur smaller sizes, you can add one of those letter at the end of your filename:
s = Small Square (90×90)
b = Big Square (160×160)
t = Small Thumbnail (160×160)
m = Medium Thumbnail (320×320)
l = Large Thumbnail (640×640)
h = Huge Thumbnail (1024×1024)
i.e. this:
http://i.imgur.com/bF8zPs4.jpg
becomes this (for the small square):
http://i.imgur.com/bF8zPs4s.jpg
Once you've set the url for the image in Glide, the image itself still needs to be downloaded and shown in the ImageView, which is causing the delay. If the images are not loading, can you check if the image url loads the image in your browser for example?
A better approach to load images in an adapter with Glide is to use a RequestManager which you pass in the constructor of your adapter. Glide will then subscribe to the lifecycle of your activity of fragment. This will prevent images from being downloaded when your activity of fragment has been destroyed, which is especially useful when Glide has to download large images.
public MyAdapter(List<Object> items, RequestManager requestManager) {
this.items = items;
this.requestManager = requestManager;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
requestManager
.load(list.get(position).getLink()) // Gets the URL in my list
.centerCrop()
.placeholder(R.mipmap.placeholder)
.crossFade()
.into(holder.imageView);
}
And then you can call the constructor of your adapter in an acitivty or a fragment as such:
MyAdapter myAdapter = new MyAdapter(items, Glide.with(this));
I've been using this approach after I've found this answer from TWiStErRob.
it is depend on your internet connection speed and the size of images.
if the size of images it to large,it may cause of of Memory exception.
In Recyclerview, Glide only loads images in visible imageViews. ImageViews that are not visible will be loaded once you scrolldown. This is due to recycler property of RecyclerView.
Add following line in you onBindViewHolder and see the logs, you will understand it:
Log.w("Link", list.get(position).getLink());
I am using Picasso to handle image loading and caching in my Android Udacity project and I am noticing the caching is not working as I'd expect:
As you can see on the left fragment, the image has already loaded in an earlier thread. Now, with the same URL link, I am asking Picasso to place that image in the fragment on the right.
Here is the code which generates the grid view on the left fragment (and occurs first):
https://github.com/esend7881/udacity-android-popmovie/blob/a9a1b9a19a37594bb5edd736b7ec59229fb5905a/app/src/main/java/com/ericsender/android_nanodegree/popmovie/adapters/GridViewAdapter.java#L71
String load = String.format(sImgUrl, sImgSize, movie.poster_path);
Picasso.with(mContext.getApplicationContext())
.load(load)
.placeholder(R.drawable.abc_btn_rating_star_on_mtrl_alpha)
.error(R.drawable.abc_btn_rating_star_off_mtrl_alpha)
.resize(550, 775)
.into(viewHolder.imageView);
And then here is the code which runs in the right fragment:
https://github.com/esend7881/udacity-android-popmovie/blob/a9a1b9a19a37594bb5edd736b7ec59229fb5905a/app/src/main/java/com/ericsender/android_nanodegree/popmovie/fragments/MovieDetailsFragment.java#L308
Picasso.with(getActivity().getApplicationContext())
.load(String.format(sImgUrl, sImgSize, mMovieObj.poster_path))
.error(R.drawable.blank)
.fit()// .resize(366, 516)
.into(mMovieThumb, new com.squareup.picasso.Callback() {
#Override
public void onSuccess() {
Utils.log(sw.toString());
Utils.hideViewSafe(mMovieThumbProgress);
}
#Override
public void onError() {
Utils.log(sw.toString());
Utils.hideViewSafe(mMovieThumbProgress);
}
});
I am using the same application context in each as well as the load text:
String.format(sImgUrl, sImgSize, mMovieObj.poster_path))
and
getActivity().getApplicationContext()
So, I would think Picasso ought to detect when the exact same URL load link appears in the same context within a short period of time from each other and Picasso would then load the exact same image back into the app.
If this is not how Picasso caching works, then how does it?
As a comment mentioned, I'd guess this is affected by the size of the image being different in both fragments.
I'd recommend using https://github.com/facebook/fresco instead of picasso. It's more efficient, especially with different sizes. You can also directly access cached files if required https://github.com/facebook/fresco/issues/80
It's probably related to the HTTP headers received when getting the image that do not allow caching, as Picasso relies on an HTTP component to do the caching.
Try uploading your image on imgur, try hardcoding that path and see if it works. If that's the case, you'll have to find a workaround on how to get the image from the movie database.
In my Android Activity, I want to display about 10 images from web. Normally, I have to create 10 ImageView to display each image, but ImageView may cause Out of memory exception. So I decide to use 10 WebView to display image. It worked great.
But I don't know how performance of WebView, can anybody advise me how performance of 10 WebViews and Can I use this method to display images?
WebView occupies more space in memory then image view . you won't get proper scaling in webView just by simply displaying images. Webview can be faster in popping image to Ui but its not the efficient way .
You can try Volly which has in build tool for image downloading,caching and scaling and i can insure you its quite fast also.
All you need to do is add the Volley library to your project and replace ImageView in your XML layout with com.android.volley.toolbox.NetworkImageView.
Add the following variables to your Activity class:
private RequestQueue mRequestQueue;
private ImageLoader mImageLoader;
Create the objects in the onCreate() method of your Activity:
mRequestQueue = Volley.newRequestQueue(context);
mImageLoader = new ImageLoader(mRequestQueue, new ImageLoader.ImageCache() {
private final LruCache<String, Bitmap> mCache = new LruCache<String, Bitmap>(10);
public void putBitmap(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
public Bitmap getBitmap(String url) {
return mCache.get(url);
}
});
Then download the image the getView() method of your Adapter class:
NetworkImageView image = (NetworkImageView)view.findViewById(R.id.image);
image.setImageUrl("http://someurl.com/someimage.png",mImageLoader);
In production code, you would use a global instance of both the RequestQueue and ImageLoader classes, and your onCreate() method wouldn't be cluttered as it is in this toy example.
I wouldn't use a webview to display images, though of course it can be done. If you really want to see which way is "faster", you can try out ImageView, NetworkImageView and WebView to load a large image and get a rough time estimate with the System.nanoTime() method.
Do you display 10 images at the same time ?
If yes, the size of the image view must be relatively small, so you can use small version of your images and avoid out of memory errors (or download the real size but create in memory a small version).
If not, you should use a ListView or GridView or ViewPager or something that recycle the views. You only get a couple of them in memory at the same time and avoid out of memory errors.
Furthermore, there are excellent libraries to display download and display images, for instance Universal Image Loader will do everything in the background, resize the image to the required size, you can activate the memory cache / disk cache, etc.
I'm trying to create a image Gallery using ViewPager. I've found several examples and I've got them all to work. The one thing the examples don't show, is how to populate the ViewPager using downloaded images from an URL. All the tutorials either have the images in the Drawables folder or on the SD card. Does anyone know of an example where a ViewPager gets the images from a URL. I've found examples of Lazy Loading a GridView and a ListView, but I'm not smart enough to be able to convert those into a working ViewPager. I mainly run into issues with the Adapter classes. The Lazy Loading examples seem to use BaseAdapter and I think a ViewPager needs to use PagerAdapter right? So, are there any examples of a ViewPager that gets the images from a URL? What I need the ViewPager to do is to download the images from a String Array something like this:
String[] imageGalleryStr = {
"http://www.website.com/images/image01.png",
"http://www.website.com/images/image02.png",
"http://www.website.com/images/image03.png",
"http://www.website.com/images/image04.png",
"http://www.website.com/images/image25.png",
"http://www.website.com/images/image26.png",
"http://www.website.com/images/image27.png",
"http://www.website.com/images/image28.png" };
Any ideas?
use this library for image galary https://github.com/nostra13/Android-Universal-Image-Loader
I haven't tried this code but hopefully something like this should work for you. :)
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
tagShown = false;
View view = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.image_view_item, container, false);
imageView = (ImageView) view.findViewById(R.id.imageView);
new DownloadImageInBackground(imageView).execute(imageGalleryStr[position]);
((ViewPager) container).addView(view);
return view;
}
...
private class DownloadImageInBackground extends AsyncTask<String, Void, Bitmap>{
ImageView iv;
public DownloadImageInBackground(ImageView iv) {
this.iv = iv;
}
#Override
protected Bitmap doInBackground(String... params) {
// download bitmap from string url
return bitmap;
}
#Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
iv.setImageBitmap(result);
}
}
I would recommend you use the Volley library, the same library used by Google in its application Google Play. Please check this answer: Getting Bitmap images from server to set on ImageView
If you already have a working template (ViewPager) maybe you could choose one of the libs for getting images/bitmaps from across the net and then wrap a loader for images inside the Fragment type being operated on by the ViewPager...
When the pager calls 'onCreateView' on the type being paged, you have something like:
imgLoadr.DisplayImage(mMap.get("pic"), imgView);
Your implementation of 'DisplayImage' is built on one of the many libs for networked image management and includes the following details:
DisplayImage implementation looks first for a properly scaled bitmap in memory...
then it looks for a bitmap in local file system cache...
then it fetches the file from across the network and feeds the network stream to get instances of local bitmap/caches.
Your post only concerned the last item above, but IMO you may have to become interest in all of the list because that is just part of working with an image-centric app..
If you are going to be dealing with large album of photos, you probably will have to look at efficiently managing each bitmap and scaling the bitmaps in some way to cut your requirements for memory.