user3764893
user3764893

Reputation: 707

How to combine an imageview with some text in listview

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

Answers (2)

Xaver Kapeller
Xaver Kapeller

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.


1) Implementing a custom 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!


2) How to use it

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

Luciano Rodr&#237;guez
Luciano Rodr&#237;guez

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

Related Questions