I have a ListView that should display items that contain some text and an image.
The data is loaded from a local SQLite db file (that contains the text and image URLs).
I'd like to:
Get the text, url from DB.
Asynchronously download the image from the URL
Bind both values to the ListView (using the SimpleCursorAdapter).
So far, i was able to read the values from DB, however i am not sure how i can run the bind only AFTER i have successfully loaded each image?
In other words, i'd like to asynchronously bind each element as it's loaded to the appropriate UI item.
Here is a nice example which shows how to this http://www.androidkit.com/loading-images-from-remote-server-over-http-on-a-separate-thread.
Briefly,
1) you need to have a Map<Url, Bitmap>.
2) Have a default image that is displayed when image data from server is not available yet.
3) Have onScroll listener for your ListView, to know which items are currently displayed.
4) First, download those that are being displayed.
5) Once an image is downloaded, call notifyDataSetChanged() to bind available Image to the view.
6) You can use Softreferences or LRUCache to avoid OutofMemoryException
I have solved a similar problem to this. I received a XML from server and store the information in a Database. After that I populated the list using the CursorAdapter. In my case I have both images and text.
To solve the problem in the cursor adapter I did something like this:
#Override
public void bindView(View v, Context ctx, Cursor c) {
TextView title = (TextView) v.findViewById(R.id.titleID);
title.setText(c.getString(c.getColumnIndex(yourColumName)));
ImageView i = (ImageView) v.findViewById(R.id.ImageID);
String s = c.getString(c.getColumnIndex(youtImageColumn));
imageLoader.DisplayImage(s,i);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View v = inflater.inflate(R.layout.yourRowLayout, parent, false);
return v;
}
In this case the ImageLoader is an async lazy image loader gotten from here:
https://github.com/thest1/LazyList
Related
I am querying the SQLite database and putting the data into a List View. One of the database rows contains an image Url field (which can also be a Uri).
The images are loaded as they should but as soon as I scroll the list all the images start flickering, some are changing places or displaying in the different places.
I already understood that this behavior is happening because the List View is reusing rows on scroll, but I have no idea how to fix this behavior. Also I cannot use external libraries like Picasso in this project.
Here is my adapter code:
public class FilmsListCustomAdapter extends CursorAdapter {
private LayoutInflater cursorInflater;
public FilmsListCustomAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
cursorInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
TextView filmTitle = (TextView) view.findViewById(R.id.filmListTitle);
TextView filmScore = (TextView) view.findViewById(R.id.filmListScore);
ImageView filmImage = (ImageView)view.findViewById(R.id.filmListPoster);
ImageView filmSeen = (ImageView)view.findViewById(R.id.filmListSeen);
String title = cursor.getString( cursor.getColumnIndex("title") );
String score = cursor.getString(cursor.getColumnIndex("score"));
String url = cursor.getString( cursor.getColumnIndex("url") );
int seen = cursor.getInt( cursor.getColumnIndex("seen") );
if(Patterns.WEB_URL.matcher(url).matches()){
LoadImage loadImage = new LoadImage(context,filmImage);
loadImage.execute(url);
}
else{
Bitmap bmp = BitmapFactory.decodeFile(url);
CamImage camImage = new CamImage(context,Uri.parse(url));
Bitmap rotetedIm = camImage.rotateCamImage(bmp,url);
if(rotetedIm!=null){filmImage.setImageBitmap(Bitmap.createScaledBitmap(rotetedIm, 850, rotetedIm.getHeight(), false));}
else{filmImage.setImageResource(R.drawable.no_poster);}
}
GlobalMethods methods = new GlobalMethods(context);
filmTitle.setTypeface(methods.getWalkFont());
filmTitle.setText(title);
filmScore.setText(score);
if(seen==1){filmSeen.setImageResource(R.drawable.eye);}
else{filmSeen.setImageResource(0);}
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
return cursorInflater.inflate(R.layout.film_row, viewGroup, false);
}
}
What is probably happening is this:
You get a web URL for an image and queue an AsyncTask to download it for an ImageView
You scroll and the ImageView is recycled
This time the ImageView gets a local URL, so you do that immediately
The previously queued AsyncTask completes and loads a now unrelated image over the image you just put in
The key to clearing this up is to make sure to cancel the task once the ImageView is recycled. One way you can do this is to put a reference to the AsyncTask in a tag in the ImageView. When you get a recycled ImageView, you check the tag and see if there is a task in progress and cancel it before you start a new task.
You should check out this article on Android Developers Blog, it will explain a little more about the problem and how to fix it:
Multithreading For Performance | Android Developers Blog
I think this article was written back when AsyncTasks were changed to run in parallel threads, since they talk about tasks completing out of order. They've since reverted to serial execution so I don't think that part applies anymore, but the concept is similar since your immediate loading of the local image acts like a task executing out of order.
Two other things I would consider:
In getView, always call imageView.setImageBitmap(null) first to clear out any leftover image in the recycled ImageView. I like to init ImageViews witth a very neutral gray bitmap that represents an "Image Loading" state.
Use an AsyncTask to decode the local files as well as retrieve the web files. I'll bet your list scrolling will seem a lot smoother when you do this.
I have viewflipper which contains 3 childs, one GridView and two custom ListView, every view has different adapter , and every adapter has a image loader with Universal Image Loader library.
The items are the same for all adapters, my goal is to show content in different way (grid , list , and big list), but in this way every image loads 3 times. Is there any way to load images once and show them to their childs?
So you need an object common to the three views, which manages image loading and holds memory. You have it: it's the Adapter. Use a single one and just switch layouts.
For instance, you could define this method inside the adapter:
int layoutResId;
public void changeLayout(int layoutResId) {
this.layoutResId = layoutResId;
notifyDataSetChanged(); //force the adapter to call getView() again
}
Then in your getView() method you just inflate the layout defined by layoutResId.
Well the thing that you seem to be asking is if you can pass the images along to the other adapters. It would be something like if image is not empty them use it in the other views, if not then use the image loader.
public class Constants {
public static Constants INSTANCE = new Constants();
public Constants() {
}
public Uri IMAGE_PATH = Uri.EMPTY;
public File IMAGE_FILE = null;
public Bitmap IMAGE = null;
}
Presumably you would choose only one of these kinds to save from your view. And then check them to see if constants possesses an image for you to display.
I am using a GridView and a CursorAdapter. All items in GridView have different types, so that my database query has many subqueries and is very slow.
So I decide to use a simple query (select only the ids).
All subqueries should be outsourced asynchronous in CursorAdapter like this (dummy code). And after the async database queries the GridView item is updated.
#Override
public void bindView(View view, Context context, Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndex(Columns._ID));
ViewLoader.queue(id, view);
}
private class ViewLoader {
public queue(long id, View view) {
Bitmap bitmap = ... // load bitmap from web by using id
String title = ... // query title from database by using id
String subtitle = ... // query subtitle from database by using id
TextView titleView = (TextView) view.findViewById(R.id.title);
titleView.setText(title);
...
// update GridView
}
What is the best approach?
I dont think that this is the best approach.
All of the thread swithing between UI and back thread will make your work unprodutable.
I had used a Cursor that queried over 4 tables with about 30-40 columns and it worked ok, i can even say pretty fast. My point is that i think that the issue is with your binding. Images can and should be loded async (i use Picasso, but a lot of libraries can do the trick). My guess is that u start with the query (try to limit it to 30 rows and then us pagination technique to load more) and work trough the binding and everything will workout perfectly. Ask freely if i didnt explain myself.
I read this question where it says not to worry about it but I guess I need some reassurance.
My custom CursorAdapter's bindView:
#Override
public void bindView(View view, Context context, Cursor c) {
// get handles for views in xml
ImageView imageView = (ImageView)view.findViewById(R.id.add_lvrow_image);
TextView titleView = (TextView)view.findViewById(R.id.add_lvrow_title);
// get data from cursor and "massage" if necessary
String imageUri = c.getString(c.getColumnIndex(CollectionsTable.COL_IMAGEURI));
String title = c.getString(c.getColumnIndex(CollectionsTable.COL_TITLE));
// terrible time getting run-time sizes of imageView, hardcode for now
int XML_WIDTH = 100;
int XML_HEIGHT = 100;
Log.d(TAG, SCOPE + "bindView called: " +count);
count++;
// use static util class
ImageUtils.loadBitmap(context, imageUri, imageView, XML_WIDTH, XML_HEIGHT);
I'm following the series of Android tutorials for loading large bitmaps but have moved decodSmapledBitmapFromUri, calculateInSmapleSize, loadBitmap, BitmapWorkerTask, AsyncDrawable, cancelPotentialWork, and getBitmapWorkerTask to a utility folder.
...so I'm calling loadBitmap and it's chain 77 times for a listview that currently has 12 rows in it (six show on the screen at load time with just a hint of the 7th showing).
So I shouldn't worry, this is okay (this number of calls to bindView & the firing off of all those subsequent methods)?
Thanks for your words.
If in your listview xml android:layout_height is not "match_parent" change it to android:layout_height="match_parent"
The newView method is called for each newly created view while the bindView is called once each view data is required to bind to the corresponding view. One of the reasons that cause bindView get called several times is listview recycling which recycles the views that goes out of viewport. As an example when you scroll over a listview every new view which comes to view port would cause a call to bindView. I suggest you to create a cache mechanism if your loadBitmap is resource intensive so that you shouldn't have a new call to loadBitmap for each bindView call.
I want to load data when user scroll . The loaded data is combine at the end of the ListView.
I test this feature . I found endless scroll in android .But I use this when the user scroll at the end of the list loading still appear and can't click on listitem . How can I know when the data end ?
For example, you could use this code within your getView method:
#Override
public View getView(final int position, View row, final ViewGroup parent) {
...
if (position > lastViewed && position == getCount()-1) {
lastViewed = position;
runTask(); //I immagine an AsyncTask which fetch data from the net or from a local db
}
...
}
Of course in your hypothetical AsyncTask you do not have to clear the adapter on onPreExecute but just adding the new items.
Sorry but the question is a bit too generic, without knowing the stucture of your code it comes complicated to answer, anyway I hope it will help.