Custom SimpleCursor adapter BindView calls - android

I wanted to know if there is a way to make the BindView work only once per item?
Are items that we scroll away from the screen getting destroyed? and when they come back we must run bindview again?
The reason is I got my adapter to Download an image and set it on the item.
when i scroll the image is getting downloaded again, even if i didnt quit the application.
the bindview is called each time an item "returns" into display so even tho i already downloaded it, it will preform the asyntask again ( the asyntask is called in the adapter)
I even tried saving BLOB on my database after each download, to know when to call the asyntast and when just to make a bitmap out of the array
if(photoBArray != null){
Bitmap bitmap = BitmapFactory.decodeByteArray(photoBArray, 0, photoBArray.length);
holder.icon.setImageBitmap(bitmap);
}else{
holder.icon.setImageResource(R.drawable.loader);
String urlString = "https://maps.googleapis.com/maps/api/place/photo?maxwidth=100&photoreference="
+ c.getString(c.getColumnIndex(PlacesDBHelper.PHOTO_REFERENCE_COL))+"&key=API_KEY";
Log.e("Photo REFERENCE", c.getString(c.getColumnIndex(PlacesDBHelper.PHOTO_REFERENCE_COL)));
try {
URL url = new URL(urlString); // create new URL object from url string
new ImageDownloader(holder.icon, this).execute(url);
}catch (Exception e){
Log.e("Error in url", e.getMessage());
}
}
I am using chrisbanes pulltorefresh listview as well, im not sure if thats the casue for the problem
Bottom line question: how to make an item look steady and ready when he gets back into the screen and not beeign created again? ( cause even if i dont dowloand an image and only setting a bitmap, the action of setimagebitmap might be visible on a slow phone)
Thanks in advance!

The entire point of ListView is to recycle row views. They aren't destroyed, they are passed back to the adapter to be bound with new data for another row. It is expected that getView (or bindView in the case of cursor adapters) will be called again if you scroll an item off screen far enough and then scroll it back on screen.
What you need is an image caching layer between the download task and the adapter. When bindView happens, first check the cache to see if the image is there. If it is, use it. If it's not, download it and add it to the cache, then use it.

Related

Android smooth ListView scrolling while loading contact photos

I am using this method to load contacts photo, in my extended CursorAdapter (With LoaderManager):
private Bitmap loadContactPhoto(Uri contactUri) {
ContentResolver cr = getActivity().getContentResolver();
InputStream stream = Contacts.openContactPhotoInputStream(cr, contactUri);
return BitmapFactory.decodeStream(stream);
}
My contacts ListView using QuickContactBadge at each item, and the ListView does not scroll smooth, even if the contacts have no images.
Can I load the images in AsyncTask or make the loading faster?
Use this generally used method to heavily optimize your listviews via recycling:
Youtube
You should tailor the above method to work with the general method, ie only use the code you gave only so many times.
However, if you've already tailored the general optimization method to your needs as is and it's still quite slow, you SHOULD use an AsyncTask. The following link should get you set up- just use a placeholder image until the task is done, then save the image with the view object so you don't have to get the image again when the user re-scrolls through.
Making ListView Scrolling Smooth

android adapter notifydatasetchanged not working properly

I have issue when image is not loaded after notifyDataSetChanged In overall i have startup class where i begin loading images from web and save them on device. And adapter uses images saved on device. So if i open my listview before i save images i see only my placeholder. And then if i return yo app then of course imageas ar loaded because theyre saved already.
creating adapter
adapter = new GridAdapter(getActivity(), grid, 6);
grid.setAdapter(adapter);
Here i save image and notifying adapter.
try {
FileOutputStream fos = context.openFileOutput(String.valueOf(id)+".jpg", Context.MODE_PRIVATE);
fos.write(imageBytes);
fos.close();
try {
GridFragment.adapter.notifyDataSetChanged();
} catch (Exception e) {
// TODO: handle exception
}
.
And in adapter i use imageloading library but simply it just sets image from device(not async etc.)
iLoader.displayImage("file:///"+mContext.getFileStreamPath
(String.valueOf(position)).toString()+".jpg", holder.imageView, DataHolder.options, this);
generally for an Adapter, notifyDataSetChanged only works if you use the add, insert, remove, and clear functions on the Adapter.
for e.g , if you have already added references to your images in the adapter (which seems probable , because the file already exists and you are writing new data to it), then if any property of your image changes (in your case, size) , notifyDataSetChanged would not do anything. it can only detect addition, removal.
(you haven't added your GridAdapter implementation, so that is all i can think of yet)

Android Volley - Cancel NetworkImageView request in Base Adapter

As the title says, I'm using a BaseAdapter to display items in a ListView. Obviously a ListView will reuse views, including TextViews and NetworkImageViews.
Assuming 3 items can be displayed at once, the NetworkImageView will be reused for items at index: 1, 4, 7, ....
Depending on what's being displayed, the NetworkImageView will either:
request the image from the Network and display it,
display a cached Bitmap,
or display a local drawable resource.
Items 2 and 3 work fine, however in Scenario 1, let's say we're displaying item at index 4 from the network, and the user scrolls to item 7 before 4 is loaded and it's a local resource, we display the local resource. However our network image request may just be finishing now, so we end up displaying an incorrect image.
How can I enforce the proper (expected)behavior?
The answer from #Snicolas is spot on, but lacks some pointers on how to actually accomplish that. So here goes.
The general idea is to keep track of the ongoing image requests for every row. That way, when you encounter a recycled row, you can cancel the pending request and kick off a new one for the new data relevant to that row.
One straightforward way to accomplish that is to make the ImageContainer that you can get back when requesting an image load, part of the adapter's ViewHolder/RowWrapper. If you're not using this pattern yet, you should. Plenty of examples out there, including a good I/O talk.
Once you've added the ImageContainer to your holder, make an image request and store the container that you get back. Somewhat like this:
ImageListener listener = ImageLoader.getImageListener(holder.imageview, defaultImageResId, errorImageResId);
holder.mImageContainer = ImageLoader.get(url, listener);
The next time a recycled row comes in the adapter's getView() method, you can get your holder back from it and check wether it has a ImageContainer set. One of the following 3 scenarios may apply:
There is no ImageContainer, which means you're good to go to make a new image request.
There is an ImageContainer and the url that it is loading is the same as for the new row data. In this case you don't have to do anything, since it's already loading the image you're after.
There is an ImageContainer but the url that it is loading is different from the new row data. In this case, cancel the request and make a new one for the current row data.
If you like, you can move some of this logic by having your BaseAdapter extension implement AbsListView.RecyclerListener (and set the adapter as recycler listener for the ListView or GridView). The onMovedToScrapHeap(View view) method gets passed in the view that has just been recycled, which means you can cancel any pending image requests in there.
You don't need to enforce anything if you use the provided NetworkImageView.
NetworkImageView detects when it has been recycled and cancels the request automatically.
I don't know this API but my guess is that you should cancel any pending request before recycling such a view. How you can do that I can't say.
Did you here of alternatives like :
picasso ?
robospice UI Module ?

Images in ListView search results

I have a ListActivity that launches a task to hit a web service and display the results in a ListView. Each one of the results has an image ID attached to it.
I wrote a method that will get the image IDs of the rows displayed on screen (firstVisiblePosition() to lastVisiblePosition()) and launch a task to query another web service to get the images to display for those items. I call this method when the list's scroll state becomes SCROLL_STATE_IDLE. This makes it so the user can scroll and the task to get the images for the visible rows does not execute until the scrolling stops, preventing it from looking up images for off-screen rows.
My issue is that when the results initially show in the ListView, I can't find a good way to call my method to look up which image IDs to query for. Apparently, calling this method right after calling setAdapter does not work (I'm guessing because some of the ListView's work happens asynchronously). I am using multiple Adapter's (for reasons not pertinent to this post), so I need a good way of waiting for the list items to show before I call my method to get the IDs.
Any ideas?
After you've set the adapter or called notifyDatasetChanged() on the adapter, add your "load images" code to the list's post queue as a Runnable:
list.post( new Runnable() {
#Override
public void run() {
//do stuff
}
});
If I'm understanding your question right, you're having trouble loading images over the net and performance issues; if so,
I would create a simple image cache in my Adapter such as a local but global HashMap:
private HashMap&ltString, Drawable&gt imgCache = new HashMap&ltString, Drawable&gt();
then in my getView() method, I would asynchronously (using a Thread and a Handler) load the images and save loaded images in my imgCache by assigning position as the key and loaded images as Drawables.
final Handler h = new Handler() {
public void handleMessage(Message msg) {
if(msg.obj != null) {
Drawable drawable = (Drawable)msg.obj;
image.setImageDrawable(drawable);
image.postInvalidate();
image.requestLayout();
imgCache.put(cacheKey, drawable);
}
}
};
loadImage(myImageView, imageURL, h); // threaded method which loads the images from net
also, in my getView() method I would first ask imgCache to see if the image already exist before loadImage is called.
This should optimize your list and rescue you from using multiple Adapter etc.
Hope this helps,
-serkan

Lazy-loading images in ListView on Android

I implemented the lazy-loading images in my ListView.
I use a AsyncTask to download the image from the internet and bind it to the ImageView in the UIThread.
It's working except that when I scroll the ListView vary fast, the downloaded images sometimes are binded into the wrong items in the list.
I guess the problem is from the reuse of convertView in the BaseAdapter.
Any ideas to solve it?
Many thanks.
EDIT:
I post the answer as following:
public void setBitmap(int position, Bitmap image) {
View itemView = mListView.getChildAt(position - mListView.getFirstVisiblePosition());
if (itemView != null) {
ImageView itemImageView = (ImageView) itemView.findViewById(R.id.item_imageview);
itemImageView.setImageBitmap(image);
}
}
There are two problems that will arise during lazy loading of images in a ListView.
The old images are still shown until the new ones are loaded. This is easy just set the ImageView to an image is loading view or set it to invisible before starting the image download.
The second problem is harder to solve. Imagine you are scrolling very fast through your list. Now your views may be recycled before the old AsyncTask has finished loading the image. You now have two tasks running that in the onPostExecute method will set an image to the imageview. Now for a short time the wrong image will be shown until the second Task finishes, or even worse for some network related reason they don't finish in the order they started and you have the wrong image overwrite the correct image. To solve this you have to check what image should be displayed after the task finished. In the View class are two methods for things exact like this one:
setTag and getTag You can bind any object to the imageview that comes into your mind. In most of the cases I use setTag to bind the URL of the image as a String to the imageview before I start a task. Now I can cast getTag to a String after the task finished and compare the URL that should be displayed with the URL that I downloaded and only set the image if necessary.
Create a function called void setBitmap(Bitmap bitmap, int position) or similar in your adapter. Let your AsyncTask call this method when a new bitmap is available. This method may then call notifyDataSetChanged() on the UI-Thread itself to ensure the views get refreshed. Holding references to views in an adapter (even by holding them in an AsyncTask) is dangerous!

Categories

Resources