× {{alert.msg}} Never ask again
Get notified about new tutorials RECEIVE NEW TUTORIALS

How to combine an imageview with some text in listview

Xaver Kapeller
Mar 13, 2015
<p>To achieve what you want to do you have to create a custom <code>Adapter</code>. To download the images I suggest you use a library like <a href="http://square.github.io/picasso/" rel="nofollow"><strong>Picasso</strong></a>. 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 <code>ImageView</code>:</p> <pre><code>Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView); </code></pre> <p>It already caches images and can also transform images in many ways. Picasso is a very powerful yet easy to use library.</p> <hr> <h1>1) Implementing a custom <code>Adapter</code></h1> <p>First we need to create a layout for each row in the <code>ListView</code>, in your case since you want to display an image and a text it needs to contain a <code>TextView</code> and an <code>ImageView</code>:</p> <pre><code>&lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"&gt; &lt;ImageView android:id="@+id/imageView" android:layout_width="30dp" android:layout_height="30dp" android:layout_margin="10dp"/&gt; &lt;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"/&gt; &lt;/RelativeLayout&gt; </code></pre> <p>Now we need to create a container class - called view model - to hold the data which belongs in each row of the <code>ListView</code>. In your case this view model contains the text you want to display and the url to the image:</p> <pre><code>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; } } </code></pre> <p><code>ListViews</code> use view recycling. We can speed up the performance of the <code>ListView</code> by using a pattern called "view holder". Basically we save a reference to the <code>Views</code> inside each row and attach it to the row itself. That way we need to call the expensive <code>findViewById()</code> only once. This view holder class - I like to call them rows - also contain a method called <code>bind()</code> to bind the data from the view model to the <code>Views</code> in each row. We need a reference to the <code>TextView</code> and <code>ImageView</code> but we also need a <code>Context</code> for Picasso. I also like to define the layout associated with this row as a public constant in the row.</p> <pre><code>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); } } </code></pre> <p>Finally we need a custom <code>Adapter</code> to make this work, it's really nothing special. The only interesting part is in <code>getView()</code>. I will comment important parts if necessary:</p> <pre><code>public class ExampleAdapter extends BaseAdapter { private final List&lt;ExampleViewModel&gt; 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&lt;ExampleViewModel&gt;(); } public ExampleAdapter(Context context, List&lt;ExampleViewModel&gt; viewModels) { this.context = context; this.inflater = LayoutInflater.from(context); this.viewModels = viewModels; } public List&lt;ExampleViewModel&gt; 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; } } </code></pre> <p>And that's everything you need. It's pretty much a best practice implementation of an <code>Adapter</code>. It uses the view holder pattern for extra performance and works perfectly with the view recycling of the <code>ListView</code>. It's fast, concise and easy and leaves little room for errors made by the developer which would otherwise slow the <code>ListView</code> down. You have perfect separation between what data you want to display (that's all in the <code>ExampleViewModel</code>) and how it is displayed (that's in the <code>ExampleRow</code>). The adapter itself doesn't know about either - as it should be!</p> <hr> <h1><strong>2) How to use it</strong></h1> <p>To use the code above we first need to create the view models which hold the data we want to display:</p> <pre><code>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"); </code></pre> <p>We need to add all those rows into a <code>List</code>:</p> <pre><code>List&lt;ExampleViewModel&gt; viewModels = new ArrayList&lt;ExampleViewModel&gt;(); viewModels.add(firstRow); viewModels.add(secondRow); viewModels.add(thirdRow); </code></pre> <p>And after that we need to create an instance of the <code>ExampleAdapter</code> and pass the <code>List</code> of view models in the constructor. Finally we just need to set the <code>Adapter</code> to the <code>ListView</code>:</p> <pre><code>ExampleAdapter adapter = new ExampleAdapter(context, viewModels); listView.setAdapter(adapter); </code></pre> <p>You can modify the items displayed in the <code>ListView</code> later on with the <code>viewmodels()</code> method of the <code>ExampleAdapter</code>! You just need to remember to always call <code>notifyDataSetChanged()</code> on the <code>Adapter</code> after modifying the view models:</p> <pre><code>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(); </code></pre> <p>I hope I could help you and if you have any further questions feel free to ask!</p> <p>This tip was originally posted on <a href="http://stackoverflow.com/questions/24433983/How%20to%20combine%20an%20imageview%20with%20some%20text%20in%20listview/24434962">Stack Overflow</a>.</p>
comments powered by Disqus