Reputation: 707
So far I've been using a simple ArrayAdapter
to display some items in a ListView
. Now I also want to display images alongside the text in the ListView
. I have an AsyncTask
called DownloadImageTask
to download images. The downloading is working perfectly, but I don't know how to display an image in the ListView
and how to use the DownloadImageTask
to download the images in the ListView
.
This is the DownloadImageTask
I use to download images into an ImageView
:
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
ImageView bmImage;
public DownloadImageTask(ImageView bmImage) {
this.bmImage = bmImage;
}
protected Bitmap doInBackground(String... urls) {
String urldisplay = urls[0];
Bitmap mIcon11 = null;
try {
InputStream in = new java.net.URL(urldisplay).openStream();
mIcon11 = BitmapFactory.decodeStream(in);
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
return mIcon11;
}
protected void onPostExecute(Bitmap result) {
bmImage.setImageBitmap(result);
}
}
I have also defined an ImageView
alongside the ListView
to download the images into the layout.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<ImageView
android:id="@+id/image1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ListView
android:id="@android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
I call the DownloadImageTask
like this:
new DownloadImageTask((ImageView) findViewById(R.id.image1)).execute(url);
How can I use the DownloadImageTask
to download images and display them in the ListView
alongside the text?
Upvotes: 2
Views: 1665
Reputation: 49817
To achieve what you want to do you have to create a custom Adapter
. To download the images I suggest you use a library like Picasso. Picasso takes care of pretty much everything when downloading the images and it really can't get any easier to use it, you just need to call this to download an image into an ImageView
:
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
It already caches images and can also transform images in many ways. Picasso is a very powerful yet easy to use library.
Adapter
First we need to create a layout for each row in the ListView
, in your case since you want to display an image and a text it needs to contain a TextView
and an ImageView
:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_margin="10dp"/>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/imageView"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:textAlignment="gravity"
android:gravity="center"/>
</RelativeLayout>
Now we need to create a container class - called view model - to hold the data which belongs in each row of the ListView
. In your case this view model contains the text you want to display and the url to the image:
private class ExampleViewModel {
private String text;
private String imageUrl;
private ExampleViewModel(String text, String imageUrl) {
this.text = text;
this.imageUrl = imageUrl;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
}
ListViews
use view recycling. We can speed up the performance of the ListView
by using a pattern called "view holder". Basically we save a reference to the Views
inside each row and attach it to the row itself. That way we need to call the expensive findViewById()
only once. This view holder class - I like to call them rows - also contain a method called bind()
to bind the data from the view model to the Views
in each row. We need a reference to the TextView
and ImageView
but we also need a Context
for Picasso. I also like to define the layout associated with this row as a public constant in the row.
private class ExampleRow {
// This is a reference to the layout we defined above
public static final int LAYOUT = R.layout.list_item;
private final Context context;
private final TextView textView;
private final ImageView imageView;
private ExampleRow(Context context, View convertView) {
this.context = context;
this.imageView = (ImageView) convertView.findViewById(R.id.imageView);
this.textView = (TextView) convertview.findViewById(R.id.textView);
}
public void bind(ExampleViewModel exampleViewModel) {
this.textView.setText(exampleViewModel.getText());
Picasso.with(this.context).load(exampleViewModel.getImageUrl()).into(this.imageView);
}
}
Finally we need a custom Adapter
to make this work, it's really nothing special. The only interesting part is in getView()
. I will comment important parts if necessary:
public class ExampleAdapter extends BaseAdapter {
private final List<ExampleViewModel> viewModels;
private final Context context;
private final LayoutInflater inflater;
public ExampleAdapter(Context context) {
this.context = context;
this.inflater = LayoutInflater.from(context);
this.viewModels = new ArrayList<ExampleViewModel>();
}
public ExampleAdapter(Context context, List<ExampleViewModel> viewModels) {
this.context = context;
this.inflater = LayoutInflater.from(context);
this.viewModels = viewModels;
}
public List<ExampleViewModel> viewmodels() {
return this.viewModels;
}
@Override
public int getCount() {
return this.viewModels.size();
}
@Override
public ExampleViewModel getItem(int position) {
return this.viewModels.get(position);
}
@Override
public long getItemId(int position) {
// We only need to implement this if we have multiple rows with a different layout. All your rows use the same layout so we can just return 0.
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// We get the view model for this position
final ExampleViewModel viewModel = getItem(position);
ExampleRow row;
// If the convertView is null we need to create it
if(convertView == null) {
convertView = this.inflater.inflate(ExampleRow.LAYOUT, parent, false);
// In that case we also need to create a new row and attach it to the newly created View
row = new ExampleRow(this.context, convertView);
convertView.setTag(row);
}
// After that we get the row associated with this View and bind the view model to it
row = (ExampleRow) convertView.getTag();
row.bind(viewModel);
return convertView;
}
}
And that's everything you need. It's pretty much a best practice implementation of an Adapter
. It uses the view holder pattern for extra performance and works perfectly with the view recycling of the ListView
. It's fast, concise and easy and leaves little room for errors made by the developer which would otherwise slow the ListView
down. You have perfect separation between what data you want to display (that's all in the ExampleViewModel
) and how it is displayed (that's in the ExampleRow
). The adapter itself doesn't know about either - as it should be!
To use the code above we first need to create the view models which hold the data we want to display:
ExampleViewModel firstRow = new ExampleViewModel("First Row". "http://http://upload.wikimedia.org/wikipedia/commons/6/6f/Freiburger_Alpen.JPG");
ExampleViewModel secondRow = new ExampleViewModel("Second Row". "http://blog.caranddriver.com/wp-content/uploads/2013/05/lamborghini_egoista_three_quarter_front_view.jpg");
ExampleViewModel thirdRow = new ExampleViewModel("Third Row". "http://4.bp.blogspot.com/-vXnf7GjcXmg/UfJZE9rWc2I/AAAAAAAAGRc/x2CIlHM9IAA/s1600/aphoto49721.jpg");
We need to add all those rows into a List
:
List<ExampleViewModel> viewModels = new ArrayList<ExampleViewModel>();
viewModels.add(firstRow);
viewModels.add(secondRow);
viewModels.add(thirdRow);
And after that we need to create an instance of the ExampleAdapter
and pass the List
of view models in the constructor. Finally we just need to set the Adapter
to the ListView
:
ExampleAdapter adapter = new ExampleAdapter(context, viewModels);
listView.setAdapter(adapter);
You can modify the items displayed in the ListView
later on with the viewmodels()
method of the ExampleAdapter
! You just need to remember to always call notifyDataSetChanged()
on the Adapter
after modifying the view models:
adapter.viewmodels().remove(0); // Remove first view model
adapter.viewmodels().add(someNewViewModel); // Add some new view model
// Always remember to call this method after modifying the viewmodels.
// This will apply the changes to the ListView.
// If you forget to call this you will get an exception
adapter.notifyDataSetChanged();
I hope I could help you and if you have any further questions feel free to ask!
Upvotes: 4
Reputation: 2309
You could use an external library like Picasso or Universal Image Loader, they will give you a lot of options. Since they manage cache, you will be able to load images from urls or whatever one time and show them in several places.
You could try:
I'm not sure, but maybe you could use your DownloadClass inside the getView() method of your list's adapter in the next way:
new DownloadImageTask((ImageView) findViewById(R.id.image1)){
@Override
protected void onPostExecute(Bitmap bm) {
//set imageview src with your bitmap }}.execute(url);
}
}.execute(archivo,"investigacion");
Upvotes: 0