Reputation: 19
I'm trying to make a ListView with dynamically loaded images, using an AsyncTask to download the image and then set it into the ListView. My problem is that, while scrolling down, the images get randomly changed.
public class GetAllCustomerListViewAdapter extends BaseAdapter {
private JSONArray dataArray;
private Activity activity;
private static LayoutInflater inflater = null;
private static final String baseUrlForImage = "http://192.168.254.1/contact/images/";
public GetAllCustomerListViewAdapter(JSONArray jsonArray, Activity a)
{
this.dataArray = jsonArray;
this.activity = a;
inflater = (LayoutInflater) this.activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
return this.dataArray.length();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// set up convert view if it is null
final ListCell cell;
if (convertView == null)
{
convertView = inflater.inflate(R.layout.get_all_customer_list_view_cell, null);
cell = new ListCell();
cell.FullName = (TextView) convertView.findViewById(R.id.customer_full_name);
cell.Age = (TextView) convertView.findViewById(R.id.customer_age);
cell.mobile = (ImageView) convertView.findViewById(R.id.customer_mobile);
convertView.setTag(cell);
}
else
{
cell = (ListCell) convertView.getTag();
}
// change the data of cell
try
{
JSONObject jsonObject = this.dataArray.getJSONObject(position);
cell.FullName.setText(jsonObject.getString("FirstName")+" "+jsonObject.getString("LastName"));
cell.Age.setText(" "+jsonObject.getInt("Age"));
String nameOfImage = jsonObject.getString("id");
String urlForImageInServer = "http://192.168.254.1/contact/getImage.php?id="+nameOfImage;
new AsyncTask<String, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(String... params) {
String url = params[0];
Bitmap icon = null;
try {
InputStream in = new java.net.URL(url).openStream();
icon = BitmapFactory.decodeStream(in);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return icon;
}
protected void onPostExecute(Bitmap result) {
cell.mobile.setImageBitmap(result);
}
}.execute(urlForImageInServer);
}
catch (JSONException e)
{
e.printStackTrace();
}
return convertView;
}
private class ListCell
{
private TextView FullName;
private TextView Age;
private ImageView mobile;
}
}
Upvotes: 2
Views: 779
Reputation: 1886
I just did this recently (at 1am actually)- the problem is that the ListView is actively recycling the views. Therefore, it gets the old views which still holds the bitmaps.
The following is what you need:
[Note: Check if the Bitmap was cached earlier. If not, do the steps below]
imageView.setImageBitmap(null);
)The solution is albeit a bit complicated but works really well and smoothly if implemented correctly. If you'd like an example, feel free to check one out in action here in my current project.
Edit for requested example:
Here is some not-so-pseudo pseudocode. The main part you still need to do is actually implement the lazy loading system (which is a whole other topic). Once again, feel free to visit my project linked above and especially look at the "ImageLoaderNoCache" NetworkUtil.
public class GetAllCustomerListViewAdapter extends BaseAdapter {
private JSONArray dataArray;
private Activity activity;
private static LayoutInflater inflater = null;
private static final String baseUrlForImage = "http://192.168.254.1/contact/images/";
// The list caches bitmaps as well as target imageViews, in order of the rows
private ArrayList<RowViewData> rowViewDataList;
public GetAllCustomerListViewAdapter(JSONArray jsonArray, Activity a)
{
this.dataArray = jsonArray;
this.activity = a;
inflater = (LayoutInflater) this.activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// Initialize the list
rowViewDataList = new ArrayList<RowViewData>(dataArray.length());
// This is more of a preference, but add in every item for the rowViewDataList
for(int i = 0; i < dataArray.length(); ++i) {
// Create a new, empty rowViewData instance and put it into the list
RowViewData rowData = new RowViewData();
rowViewDataList.add(rowData);
}
}
@Override
public int getCount() {
return this.dataArray.length();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// set up convert view if it is null
final ListCell cell;
if (convertView == null)
{
convertView = inflater.inflate(R.layout.get_all_customer_list_view_cell, null);
cell = new ListCell();
cell.FullName = (TextView) convertView.findViewById(R.id.customer_full_name);
cell.Age = (TextView) convertView.findViewById(R.id.customer_age);
cell.mobile = (ImageView) convertView.findViewById(R.id.customer_mobile);
convertView.setTag(cell);
}
else
{
cell = (ListCell) convertView.getTag();
}
// change the data of cell
try
{
JSONObject jsonObject = this.dataArray.getJSONObject(position);
cell.FullName.setText(jsonObject.getString("FirstName")+" "+jsonObject.getString("LastName"));
cell.Age.setText(" "+jsonObject.getInt("Age"));
// Get the rowViewData for this position
RowViewData curRowViewData = rowViewDataList.get(position);
// If the bitmap is already cached, just use it
if(curRowViewData.bitmap != null) {
cell.mobile.setImageBitmap(curRowViewData.bitmap);
}
// Otherwise, gotta do some real work
else {
// First, set the cell's imageView's bitmap to null
// [in case this is a recycled view already with a bitmap]
cell.mobile.setImageBitmap(null);
// Then, start up the caching system
String nameOfImage = jsonObject.getString("id");
String urlForImageInServer = "http://192.168.254.1/contact/getImage.php?id="+nameOfImage;
// Create a system to load images lazily and then receive the bitmaps
// Look at my ImageLoaderNoCache for an example. Feel free to reuse it
/**
* this = this adapter should implement an interface. Look below for the corresponding method
* url = image source url
* position = position this bitmap belongs to. Will get returned along with bitmap for reference
*/
LazyImageLoader.GetBitmap(this, url, position);
// Cache/target this imageView for later
curRowViewData.targetImageView = cell.mobile;
// Double check this imageView is not being targeted already elsewhere
// ie, one of the recycling fixes [else, two bitmaps would load at once, etc]
// Also, probably could be more efficient
for(int i = 0; i < rowViewDataList.size(); ++i) {
// If this is the current position, then skip all the logic for this round
if(i == position) continue;
// Get the rowViewData for this round
// Yeah... I should've used "cell" instead of row for you...
RowViewData checkRowData = rowViewDataList.get(i);
// If the targeted ImageView is the same, null-ify it
if(checkRowData.targetImageView == curRowViewData.targetImageView) {
// The old one should be null as the bitmap should not load suddenly into
// the current recycled item
checkRowData.targetImageView = null;
// Personally, I knew that there would only be one copy at any time
// so I tried to save time by breaking out here. Feel free to be
// cautious and comment out the break statement
break;
}
}
}
}
catch (JSONException e)
{
e.printStackTrace();
}
return convertView;
}
/**
* Should be called as part of an interface with the LazyImageLoader
* @param bitmap - The lazily loaded bitmap that was requested earlier
* @param position - The row/cell position that the bitmap was requested for
*/
public void getBitmapForPosition(Bitmap bitmap, int position) {
// Get the rowViewData instance for this row/position
RowViewData curRowData = rowViewDataList.get(position);
// Cache the bitmap
curRowData.bitmap = bitmap;
// Check if the bitmap should be loaded still
if(curRowData.targetImageView != null) {
curRowData.targetImageView.setImageBitmap(bitmap);
// Not sure if strictly necessary, but good practice to make the cached view null now
curRowData.targetImageView = null;
}
}
private class ListCell
{
private TextView FullName;
private TextView Age;
private ImageView mobile;
}
// Holds all the data for lazily loading an image with a bitmap
private class RowViewData {
public ImageView targetImageView;
public Bitmap bitmap;
}
}
Upvotes: 1