The important thing to note here is that ListActivityHAS AN IMPLICIT LAYOUT associated
with it.This default layout contains a ListView that fills the screen.You are not required to define any other layout for the ListActivity,although its perfectly fine to do so.:).

I've used the onCreate method of my ListActivity to define the data that will be displayed in the ListView and to set the adapter.

public class CustomListViewActivity extends ListActivity {
//the ArrayList that will hold the data to be displayed in the ListView
ArrayList<HashMap<String, Object>> players;
LayoutInflater inflater;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//not necessary as ListActivity has an
//implicitly defined Layout(with a ListView of course)
//setContentView(R.layout.main);
//get the LayoutInflater for inflating the customomView
inflater=(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//these arrays are just the data that
//I'll be using to populate the ArrayList
//You can use our own methods to get the data
String names[]={"Ronaldo","Messi","Torres","Iniesta",
"Drogba","Gerrard","Rooney","Xavi",};
String teams[]={"Real Madrid","Barcelona","Chelsea","Barcelona","Chelsea","Liverpool","ManU","Barcelona"};
Integer[] photos={R.drawable.cr7,R.drawable.messi,R.drawable.torres,R.drawable.iniesta,
R.drawable.drogba,R.drawable.gerrard,
R.drawable.rooney,R.drawable.xavi};
players=new ArrayList<HashMap<String,Object>>();
//HashMap for storing a single row
HashMap<String , Object> temp;
//total number of rows in the ListView
int noOfPlayers=names.length;
//now populate the ArrayList players
for(int i=0;i<noOfPlayers;i++)
{
temp=new HashMap<String, Object>();
temp.put("name", names[i]);
temp.put("team", teams[i]);
temp.put("photo", photos[i]);
//add the row to the ArrayList
players.add(temp);
}
//create the adapter
//first param-the context
//second param-the id of the layout file
//you will be using to fill a row
//third param-the set of values that
// will populate the ListView
CustomAdapter adapter=new CustomAdapter(this, R.layout.players_layout,players);
//finally,set the adapter to the default ListView
setListAdapter(adapter);
}

This is whats been done here:
1. Set the names,teams and the drawables for each row as arrays.
2. Add all of these to an ArrayList players.
Hence,now we have all the data to be displayed in the ArrayListplayers sound and safe.

Now comes the important part: Creating your CustomAdapter
The adapter is the heart of the ListView.It provides the ListView with the data and the Views that will be displayed in the list.
The default implementation of the ArrayAdapter provides only a simple TextView as a list item.However,its fairly simple to create your own adapter to replace them with custom layouts.
All you have to do is define a class which extends the ArrayAdapter class and Override its getView() method. The ListView will then set the View returned by getView() as a list item.Note that getView() will be called every time a list item is displayed.
This is how its done:

I'll explain what just happened here.
The getView() method is called everytime android needs to display a list item.
We may inflate the layout and return it everytime;but android provides a brilliant yet simple mechanism for re-using the inflated Views.
The convertView parameter passed to getView() is the View recycling mechanism of android.
Android only creates the Views that are visible to the user.Sometimes,maybe one or two extra.
As the user scrolls,the View of the list item that leaves the screen can be re-used to display the list item that enters the screen.
And this recycled View is given to the getView() method as a parameter.
Thus,we require to inflate only when convertView is null.

That being said,how do you effectively re-use the convertView?? That's where the ViewHolder class comes into play.
Every time convertView is null,the layout is inflated,and an instance of the ViewHolder class is created to hold the inflated views.This instance is then tagged to the convertView.
If getView() gives a non- null convertView,then simply get the already tagged ViewHolder from it and use it.

Thus effectively,in a list with a 1000 items,we can bring down the layout inflation from 1000 times to maybe 5 or 6 times!!! Imagine the performance enhancement this technique will give,given that inflating and parsing the View hierarchy is a costly affair.