Importare risorse in Eclipse

Per importare risorse in Eclipse ci sono vari modi, a seconda di dove si vuole posizionare la risorsa nel filesystem.

Il mio modo preferito è quello di creare una directory in src e metterci dentro le varie risorse, quali icone, immagini, file audio, ecc…
Ovviamente ciò si può fare sia creando manualmente la directory, sia importando risorse in src e indicando la nuova directory.

Nel codice, si accede alla risorsa con un comando simile a questo:

// dal package java.lang.Object -> java.lang.Class<T>
// public URL getResource(String name)
myClass.class.getResource("/res/icona_32x32.png);

Vedere http://lj4newbies.blogspot.it/2008/03/using-classgetresource-load-resource.html

Java Swing, appunti

Il modo più semplice di gestire gli eventi di una finestra creata da Swing è quello di assegnare ad un componente un listener:

public class MyFrame extends JFrame {
	private JButton bottone = new JButton("Bottone");
	Ascoltatore listener = new Ascoltatore();

	public MyFrame() {
			...
			Container c = this.getContentPane();
			c.add(bottone);
			// Assegno l' ascoltatore
			bottone.addActionListener(listener);
	}
}

public class Ascoltatore implements ActionListener {
	public void actionPerformed(ActionEvent event) {
		// Accedo all' oggetto che ha scatenato l' evento
		JButton b = (JButton) event.getSource();
		JOptionPane.showMessageDialog(null, "E` stato premuto" + b.getText());
	}
}

ma il problema di questa tecnica è che non si può accedere ad un elemento diverso da quello che ha scatenato l’ evento: con la classe che implementa l’ interfaccia ActionListener posta “esternamente” alla classe del frame, è impossibile accedere agli altri oggetti del frame.

Una prima soluzione è quella di scrivere il listener internamente al frame, così da poter accedere ai suoi membri:

public class MyFrame extends JFrame {
	private JButton bottone = new JButton("Bottone");

	public MyFrame() {
			...
			Container c = this.getContentPane();
			c.add(bottone);
			// Assegno l' ascoltatore in modo anonimo
			bottone.addActionListener(new Ascoltatore());
	}

	class Ascoltatore implements ActionListener {
		public void actionPerformed(ActionEvent event) {
			// Accedo all' oggetto che ha scatenato l' evento
			// semplicemente accedendo al metodo "bottone" della classe
			JButton b = bottone;
			JOptionPane.showMessageDialog(null, "E` stato premuto" + b.getText());
		}
	}
}

Lo svantaggio di questa soluzione è che la classe del frame può diventare eccessivamente grande al crescere della complessita della GUI, e si avrà anche un codice poco leggibile con 2 classi eterogenee fuse tra loro.

La soluzione migliore è quella di definire un costruttore del listener che prende come parametro un riferimento alla finestra che contiene i vari componenti.

public class MyFrame extends JFrame {
	// Componenti della GUI come membri della classe
	JPanel centro = new JPanel();
	JPanel sud = new JPanel();
	JTextField txt = new JTextField(20);
	JButton button = new JButton("Premi");
	// Inizializzazione del listener
	Listen ascolt=new Listen(this);

	public MyFrame() {
		super("Esempio");
		centro.add(txt);
		...
		button.addActionListener(ascolt);
		txt.addActionListener(ascolt);
		...
	}
}

class Listen implements ActionListener {
	MyFrame frame;
	// Costruttore che riceve il riferimento al frame
	public Listen(MyFrame aFrame)
		{ frame = aFrame; }

	// Da qui accediamo ai metodi del frame !
	public void actionPerformed(ActionEvent e) {
	JTextField text = frame.txt;
	JOptionPane.showMessageDialog(null,text.getText());
	}
}

A questo punto bisogna dire che non si può pensare di creare un listener per ogni componente: si può invece raggruppare vari componenti ed assegnargli un unico listener condiviso, con lo stessa funzione actionPerformed condivisa tra essi.

Dunque, in actionPerformed bisogna distinguere quale oggetto ha generato l’ evento; si può fare in 2 modi:

  • con il metodo getSource() della classe ActionEvent. Questa tecnica è fattibile se si può confrontare il riferimento dato da getSource() con il riferimento memorizzato nella classe del frame: è, in generale, il caso della classe interna vista prima.
  • con la proprietà actionCommand dei componenti di swing. Con questa tecnica, in actionPerformed() non è più necessario prendere i riferimenti agli oggetti, ma basta leggere l’ actionCommand e chiamare la funzione appropriata.
    Questa tecnica è utile anche nel caso in cui ci siano diversi componenti che condividono la stessa azione: ad esempio un bottone e una voce di menu possono fare la stessa cosa, quindi basta scrivere una sola funzione e farla chiamare da actionPerformed tramite un actionCommand.
    Rimane il fatto che per accedere ai componenti del frame sia necessario passare al costruttore del listenere il riferimento al frame che contiene quei componenti come metodi della classe.

    public class MyFrame extends JFrame {
    	...
    	JMenuItem UpOpt = new JMenuItem("Up");
    	JMenuItem DownOpt = new JMenuItem("Down");
    	JMenuItem RandomOpt = new JMenuItem("Random");
    	Listener ascolt = new Listener();
    	public MyFrame() {
    		...
    		UpOpt.addActionListener(ascolt);
    		UpOpt.setActionCommand(ascolt.UPOPT);
    		DownOpt.addActionListener(ascolt);
    		DownOpt.setActionCommand(ascolt.DOWNOPT);
    		RandomOpt.addActionListener(ascolt);
    		RandomOpt.setActionCommand(ascolt.RANDOMOPT)
    		...
    	}
    }
    
    public class Listener implements ActionListener {
    	public final static String UPOPT = "up";
    	public final static String DOWNOPT = "down";
    	public final static String RANDOMOPT = "random";
    	// Eventualmente scrivere un costruttore
    	// per avere un riferimento al frame
    
    	public void actionPerformed(ActionEvent e) {
    		String com = e.getActionCommand();
    		if (com == UPOPT)
    			upOpt();
    		else if (src == DOWNOPT)
    			downOpt();
    		else if (src == RANDOMOPT)
    			randomOpt();
    	}
    
    private void upOpt()
    { ... }
    private void randomOpt()
    { ... }
    private void downOpt()
    { ... }
    }
    
    

Java Swing livello avanzato: http://home.cogeco.ca/~ve3ll/jatutorc.htm
MVC: http://www.macs.hw.ac.uk/guidebook/?name=Using%20The%20GUI&page=1

SQLite con Android

Il SO Android mette a disposizione gli strumenti per operare con database SQLite: per gestirli è sufficiente implementare una classe “helper”.

Per prima cosa creiamo un’ interfaccia in cui memorizzare i nomi delle colonne e delle tavole del database, per non doverli scrivere ogni volta: estendiamo l’ interfaccia BaseColumns

public interface ProvinciaTable extends BaseColumns {
	String TABLE_NAME = "province";
	String CODICE = "codice";
	String NOME = "nome";

	String[] COLUMNS = new String[] {_ID, CODICE, NOME};
}

si ricordi che in una interfaccia una costante è implicitamente public static final.

Creazione e popolamento del database

L’ SDK ci fornisce la classe astratta SQLiteOpenHelper per gestire “facilmente” i database SQLite: i suoi metodi onCreate e onUpgrade sono astratti, quindi siamo obbligati ad implementarli.
La classe si prenderà cura di aprire il database (se esiste), di crearlo (se non esiste), e di aggiornarlo: userà le transazioni per avere il database sempre in uno stato sensibile.

Vediamo il costruttore:

public class DatabaseHelper extends SQLiteOpenHelper {

	private static final String DATABASE_NAME = "devAPP.db";
	private static final int SCHEMA_VERSION = 1;

	public DatabaseHelper(Context context) {
		super(context, DATABASE_NAME, null, SCHEMA_VERSION);
	}
	//...
}

Lo schema_version serve per gestire gli aggiornamenti del database.
Vediamo ora il callback onCreate, che viene chiamato soltanto quando un database deve essere creato:

public void onCreate(SQLiteDatabase db) {
	// formatto una stringa sql, come si fa con printf in C, con i comandi per creare il database
	String sql = "CREATE TABLE {0} ({1} INTEGER PRIMARY KEY AUTOINCREMENT, {2} TEXT NOT NULL,{3} TEXT NOT NULL);";
	db.execSQL(MessageFormat.format(sql, ProvinciaTable.TABLE_NAME, ProvinciaTable._ID, ProvinciaTable.CODICE, ProvinciaTable.NOME));

	inserisciProvincia(db, "Agrigento", "AG");
	inserisciProvincia(db, "Alessandria", "AL");
	inserisciProvincia(db, "Ancona", "AN");
	//...
	inserisciProvincia(db, "Viterbo", "VT");
}

Per inserire i valori usiamo questo metodo:

private void inserisciProvincia(SQLiteDatabase db, String nome, String codice) {
	ContentValues v = new ContentValues();
	v.put(ProvinciaTable.CODICE, codice);
	v.put(ProvinciaTable.NOME, nome);
	db.insert(ProvinciaTable.TABLE_NAME, null, v);
}

il metodo insert della classe SQLiteDatabase richiede il terzo parametro di tipo ContentValues: questo serve a “mappare” un dato nella rispettiva colonna.
Il secondo parametro invece è opzionale e riguarda la gestione dell’ inserimento di una riga di dati nulli: se posto a true, fa si che vengano inseriti dei NULL nel database invece di restituire errore.

Esecuzione di una query

I metodi finora visti servono a creare e popolare il database, ma ancora non abbiamo incontrato un evento che possa chiamarli.

La classe SQLiteOpenHelper offre due metodi per accedere al database:

  • public synchronized SQLiteDatabase getReadableDatabase ()
  • public synchronized SQLiteDatabase getWritableDatabase ()

Il primo metodo crea o apre il database e restituisce lo stesso oggetto che restituirebbe getWritableDatabase, a meno che non sorga un problema che costringe ad aprire il database in sola lettura (ad esempio un disco pieno).
Il secondo metodo è più restrittivo: crea o apre il database, ma richiede esclusivamente un accesso in scrittura, e lancia una SQLiteException se ciò non è possibile.

Dunque, per accedere al database (eventualmente dopo averlo creato ex-novo), è necessario chiamare uno dei due metodi ora visti.
Implemento allora un nuovo metodo che chiamerò dalla Activity principale:

public Cursor getProvince() {
	return (getReadableDatabase().query(
		ProvinciaTable.TABLE_NAME, ProvinciaTable.COLUMNS, null, null, null, null, ProvinciaTable.NOME));
}

dentro questo metodo avviene la chiamata a getReadableDatabase() che ci assicura un accesso al database (sarebbe più raffinato creare un blocco try/catch SQLiteException per controllare eventuali errori), ed effettuo una query sul SQLiteDatabase restituito.

Tra i vari metodi query uso

public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)

i cui parametri sono:

  • il nome della tabella
  • l’ array con i nomi delle colonne (vedere interfaccia creata all’ inizio)
  • 4 parametri per definire la clausola WHERE, argomenti per il bind, la clausola GROUP BY, e la clausola HAVING
  • la clausola ORDER BY (che nello snippet ho impostato a ProvinciaTable.NOME)

L’ oggetto restituito dalla query è Cursor (che ricorda il database handler del PHP): infatti nella Activity dovremo ciclare su di esso per estrarre le righe.
Vediamo come si estraggono i dati:

// siamo nella Activity principale
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	// istanziamo l' helper della connessione
	DatabaseHelper databaseHelper = new DatabaseHelper(this);
	// eseguiamo la query
	Cursor c = databaseHelper.getProvince();

	try {
		while (c.moveToNext()) {
			Log.d("devAPP", c.getLong(0) + " " + c.getString(1) + " " + c.getString(2));
		}
	} finally {
		c.close();
	}
}

il codice è abbastanza semplice ed autoesplicativo.
Faccio solo notare i metodi moveToNext, getLong, e getString: il primo serve a muovere il cursore tra il recordset restituito dalla query (vedere la classe Cursor per altri metodi simili); gli altri due leggono i dati rispettivamente di tipo numerico (Long) e di tipo String.
Infatti la query viene eseguita sulle colonne ProvinciaTable.COLUMNS (vedere funzione getProvince()) che sono definite nell’ interfaccia (vedere all ‘inizio) ProvinciaTable come

String[] COLUMNS = new String[] {_ID, CODICE, NOME};

Conclusione

Al lancio dell’ applicazione viene istanziato il nostro DatabaseHelper, poi viene eseguita la query tramite databaseHelper.getProvince(): questa funzione chiama getReadableDatabase() che, se non trova il database, lo crea ex-novo, e lo fa chiamando il callback onCreate di DatabaseHelper.

Nella Activity ho usato la funzione Log.d(…) della classe Log: questa scrive nel log di Eclipse; per vederlo andare nel menu Window->Show View->Other e in “Android” selezionare Log Cat.

Android SDK, Fragment

I Fragment sono oggetti molto simili alle Activity, solo che possono essere inseriti e rimossi dinamicamente, e possono esserne presenti più di uno nella stessa schermata.

Come inserire Fragment

Un modo per usare i Fragment è quello di creare un layout XML apposito: res/layout/news_article.xml e il suo elemento root sia <FrameLayout>:

<?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

    </FrameLayout>

questo elemento sarà il contenitore dei frammenti che saranno aggiunti programmaticamente.
C’è anche la possibilità di creare layout con frammenti per mezzo di XML, ma non sarà possibile poi modificarlo dinamicamente.

Per inserire questo layout nell’ Activity, usare il comando setContentView:

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_articles);
    ....

Per riempire il FrameLayout si opera in questo modo:

  • si prende il FragmentManager dell’ Activity in cui si trova il layout
  • si inizia una transazione
  • si eseguono le operazioni (add, replace, remove)
  • si fa il commit

ad esempio:

FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.fragment_container, headerFragment, "headerFragment").commit();

La funzione FragmentTransaction.add(int containerViewId, Fragment fragment, String tag) ha 3 parametri:

  1. un int per l’ id del container
  2. un Fragment da inserire: può anche essere una sottoclasse con metodi sovraccarichi
  3. una Stringa per identificare il frammento per recuperarlo in seguito con FragmentManager.findFragmentByTag(String)

Sostituire Fragment

Si opera come per aggiungere un Fragment, con l’ aggiunta di un nuovo comando: transaction.addToBackStack(null); che serve a salvare nel BackStack il frammento rimosso; il BackStack è gestito dall’ Activity e permette all’ utente di tornare al precedente stato premendo il tasto “back”.

Esempio:

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

Comunicazione tra diversi Fragment

E’ possibile stabilire una comunicazione bidirezionale tra un’ attività contenitrice e i suoi frammenti.
Un modo per realizzarla è quello di definire una interfaccia di callback nel frammento, e costringere l’ attività ad implementarla.
Quando l’ attività riceve il callback attraverso l’ interfaccia, può convididere informazioni con altri frammenti del layout.

Esempio di comunicazione Fragment-Activity

public class HeadlinesFragment extends ListFragment {
    OnHeadlineSelectedListener mCallback;

    // Container Activity must implement this interface
    public interface OnHeadlineSelectedListener {
        public void onArticleSelected(int position);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        
        // This makes sure that the container activity has implemented
        // the callback interface. If not, it throws an exception
        try {
            mCallback = (OnHeadlineSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnHeadlineSelectedListener");
        }
    }
    ...
}

Il precedente Fragment (è un ListFragment, una sua sottoclasse) ha una dichiarazione di interfaccia OnHeadlineSelectedListener.
Implementando questa interfaccia nell’ attività contenitrice e sovrascrivendo (overriding) il suo metodo onArticleSelected è possibile inviare l’ evento ad altri frammenti.
Il blocco try-catch serve a verificare che, quando il frammento viene inserito nell’ attività (evento onAttach() !), questa implementi l’ interfaccia: la verifica consiste nell’ istanziare una istanza dell’ interfaccia OnHeadlineSelectedListener tramite la classe dell’ attività, la quale implementa l’ interfaccia stessa.
In caso di errore, viene lanciata un’ eccezione.
In caso di successo, il membro mCallback tiene un riferimento all’ implementazione dell’ interfaccia da parte dell’ attività.

Il frammento usa l’ interfaccia di callback per inviare eventi all’ activity genitrice:

@Override
public void onListItemClick(ListView l, View v, int position, long id) {
   // Send the event to the host activity
   mCallback.onArticleSelected(position);
}

Dal precedente snippet si vede che quando avviene l’ evento onListItemClick nel frammento, il suo listener chiama l’ interfaccia di callback che precedentemente abbiamo “agganciato” all’ attività; in altre parole, quando avviene un evento nel frammento, viene eseguito prima il listener nel frammento, e questo chiama la funzione implementata nell’ attività contenitrice: in questo modo si possono passare variabili dal frammento all’ attività.

Implementare l’ interfaccia

L’ attività che riceve i messaggi deve implementare le interfacce definite nei frammenti:

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...
    
    public void onArticleSelected(Uri articleUri) {
        // The user selected the headline of an article from the HeadlinesFragment
        // Do something here to display that article
    }
}

Esempio di comunicazione Activity-Fragment

L’ attività puo inviare messaggi ai frammenti direttamente chiamando i metodi pubblici dei frammenti: può catturare l’ istanza del Fragment con findFragmentById().

Esempio:

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...

    public void onArticleSelected(int position) {
        // The user selected the headline of an article from the HeadlinesFragment
        // Do something here to display that article

        ArticleFragment articleFrag = (ArticleFragment)
                getSupportFragmentManager().findFragmentById(R.id.article_fragment);

        if (articleFrag != null) {
            // If article frag is available, we're in two-pane layout...

            // Call a method in the ArticleFragment to update its content
            articleFrag.updateArticleView(position);
        } else {
            // Otherwise, we're in the one-pane layout and must swap frags...

            // Create fragment and give it an argument for the selected article
            ArticleFragment newFragment = new ArticleFragment();
            Bundle args = new Bundle();
            args.putInt(ArticleFragment.ARG_POSITION, position);
            newFragment.setArguments(args);
        
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

            // Replace whatever is in the fragment_container view with this fragment,
            // and add the transaction to the back stack so the user can navigate back
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);

            // Commit the transaction
            transaction.commit();
        }
    }
}

Interfaccia utente, Activity, ed Intent

Interfaccia utente

L’ interfaccia grafica si basa su una gerarchia di oggetti di tipo View e ViewGroup.
Un LinearLayout è una sottoclasse di ViewGroup: al suo interno si può inserire un oggetto View, come un EditText o un Button.

Un importante attributo delle View è layout_weight: esso permette di specificare una quota dello spazio a disposizione delle View contigue.
Ad esempio: se impostassimo a 2 il peso di una View e ad 1 il peso di un’ altra, avremmo che la prima occuperà 2/3 dello spazio (infatti la somma dei pesi è 3); se ad una terza View si desse il peso 1, la prima avrebbe 2/4=1/2 dello spazio, e le altre ne avrebbero 1/4 ciascuna.
Il valore di default per ogni View è zero: quindi se si desse un peso maggiore di 0 ad una sola vista, questa occuperebbe tutto lo spazio lasciato libero dalle rimanenti View impostate a wrap_content.
Un esempio di ciò è un LinearLayout orizzontale in cui c’è una riga con EditText e un Button impostati entrambi a wrap_content per larghezza e altezza. Dando peso pari a 1 alla EditText per allargarla al massimo dello spazio disponibile, il bottone occuperebbe il minore spazio possibile per avvolgere la stringa che contiene.
In questo caso è importante anche modificare layout_width di EditText impostandolo a 0dp: infatti wrap_content richiede un calcolo della larghezza, e il layout_weight ne richiede un altro; usando 0dp si evita il doppio calcolo.

Lanciare una seconda Activity

Supponiamo di utilizzare il bottone “Send” della figura qui sopra per aprire una nuova Activity in cui mostrare la stringa inserita in EditText.

Nel main.xml aggiungere android:onClick=”sendMessage” per indicare una funzione da chiamare quando il bottone viene cliccato; nella classe della prima Activity definire la funzione

/** Called when the user selects the Send button */
public void sendMessage(View view) {
    // Do something in response to button
}

Notare che il nome della funzione deve essere uguale al nome assegnato all’ attributo XML, la funzione deve essere public, deve ritornare void, e avere un solo parametro View.
Per passare alla seconda Activity i dati si usano gli Intent.

Per esempio:

Intent intent = new Intent(this, DisplayMessageActivity.class);

il costruttore prende 2 parametri:

  • un Context: si usa this perchè Activity è una sottoclasse di Context
  • la classe del componente a cui il sistema deve consegnare l’ intent: in questo caso sarà l’ Activity che dovrà essere aperta

poi nell’ intent istanziato si possono inserire i dati da passare:

Intent intent = new Intent(this, DisplayMessageActivity.class);
EditText editText = (EditText) findViewById(R.id.edit_message);
String message = editText.getText().toString();
intent.putExtra(EXTRA_MESSAGE, message);

la funzione putExtra() prende una “chiave” (un nome di variabile) e il suo valore come parametri: bisogna creare la chiave nella Activity (siamo ancora nella prima !)

public class MyFirstActivity extends Activity {
    public final static String EXTRA_MESSAGE = "com.example.myapp.MESSAGE";
    ...
}

è sempre bene definire la chiave usando il package name dell’ applicazione come prefisso per essere sicuri che sia univoca, nel caso di interazione con altre applicazioni.

Avviare la seconda Activity

Per avviare la nuova Activity basta completare la precedente funzione con il comando startActivity(intent):

/** Called when the user selects the Send button */
public void sendMessage(View view) {
    Intent intent = new Intent(this, DisplayMessageActivity.class);
    EditText editText = (EditText) findViewById(R.id.edit_message);
    String message = editText.getText().toString();
    intent.putExtra(EXTRA_MESSAGE, message);
    startActivity(intent);
}

Ora bisogna creare la classe DisplayMessageActivity della nuova Activity; è necessario implementare il metodo onCreate() (ogni sottoclasse di Activity deve farlo) ed è in esso che si inizializzano i componenti essenziali dell’ Activity.

E’ anche necessario dichiarare l’ Activity nel AndroidManifest.xml (con Eclipse è facile aggiungere la nuova activity).

Ora si può “ricevere” l’ intent:

Intent intent = getIntent();
String message = intent.getStringExtra(MyFirstActivity.EXTRA_MESSAGE);

notare la “chiave” definita precedentemente: ora serve a recuperare la stringa.

A questo punto abbiamo tutti i dati: rimane solo da visualizzarli e per semplicità adesso lo farò in maniera programmatica (senza usare i layout XML)

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Get the message from the intent
    Intent intent = getIntent();
    String message = intent.getStringExtra(MyFirstActivity.EXTRA_MESSAGE);

    // Create the text view
    TextView textView = new TextView(this);
    textView.setTextSize(40);
    textView.setText(message);

    setContentView(textView);
}

Android 4.0.3 ListView

Analisi di un semplice programma in Java per Android, scritto con Eclipse 3.7.2.

Per prima cosa creo un nuovo file XML da mettere in res/layout/ che chiamo list_item.xml, e che è un file che descrive il layout della singola riga che andrà a formare la lista: con la IDE è facile creare una nuova TextView

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
</TextView>

sempre con l’ aiuto della IDE si può modificare il layout a piacimento.
Inoltre preparo i dati da rappresentare nella lista: in res/values/strings.xml aggiungo un’ array di stringhe del tipo:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">ProvaListView</string>

    <string-array name="list_countries">
        <item>Afghanistan</item>
        <item>Albania</item>
        <item>Algeria</item>
        <item>Andorra</item>
        <item>Angola</item>
        .......
    </string-array>

</resources>

Notare l’ attributo name=”list_countries” di string-array: sarà usato per richiamare i dati in seguito.

Poi modifico il file della Activity del programma: invece di usare questa classe, userò la sua sottoclasse ListActivity

public class ProvaListViewActivity extends ListActivity { ..... }

La classe ListActivity ha alcuni metodi specializzati per creare la lista tramite degli Adapter: questi sono un tramite fra i dati e la vista.
Quindi cancello la riga che imposta la vista con un contenuto “statico”

setContentView(R.layout.main);

e la sostituisco con il metodo public void setListAdapter (ListAdapter adapter) della classe ListActivity; il suo argomento è un oggetto ListAdapter che ha diverse sottoclassi.
In questo esempio userò la sottoclasse ArrayAdapter dichiarando T come String, cioè un ArrayAdapter di stringhe.
La classe ArrayAdapter ha diversi costruttori: userò public ArrayAdapter (Context context, int textViewResourceId, T[] objects) e perciò devo reperire l’ array di oggetti di tipo T = String.
Il tutto è riportato nel seguente snippet:

String[] list_countries = getResources().getStringArray(R.array.list_countries); 
setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, list_countries));

con la prima riga richiamo i dati da res/values/strings.xml con l’ attributo list_countries;
con la seconda creo in modo “anonimo” l’ ArrayAdapter indicando il contesto, il template “list_item”, e i dati.

A questo punto, i dati sono “collegati” all’ adapter, pronti per essere visualizzati.
Rimane da impostare la ListView.

Essa è “contenuta” nella ListActivity, e la prendo con il comando ListView lv = getListView();; ora posso impostare varie opzioni. Ad esempio imposto lv.setTextFilterEnabled(true); (che filtra gli elementi in base all’ input utente) e imposto un callback per i click sui singoli elementi: lv.setOnItemClickListener(listener); in cui “listener” è una interfaccia, con un metodo astratto “onItemClick” che rappresenta la funzione di callback vera e propria.
La definizione di onItemClick è abstract void onItemClick(AdapterView parent, View view, int position, long id), in cui AdapterView (con type generics) è l’ Adapter un cui elemento viene cliccato (in questo caso è una ListView, discendente di AdapterView), view è la vista cliccata nell’ AdapterView (il valore è fornito dall’ Adapter), position e id sono la posizione e l’ id della vista cliccata.
Un esempio di tutto ciò è:

OnItemClickListener listener = new OnItemClickListener() {
        	@Override
        	public void onItemClick(AdapterView<?> parent, View view, int position,
        			long id) {
        		TextView tv = (TextView) view;
        		Toast.makeText(getApplicationContext(), tv.getText(), Toast.LENGTH_LONG).show();
        	}
		};
ListView lv = getListView();
lv.setTextFilterEnabled(true);
lv.setOnItemClickListener(listener);

in cui il callback visualizza semplicemente un Toast con il testo cliccato (utilizzo un type casting da View a TextView).

Il risultato complessivo è:

public class ProvaListViewActivity extends ListActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /** Questo metodo aggiunge alla ListActivity un ListAdapter.
         *  Uso una sua sottoclasse: ArrayAdapter di tipo String.
         *  L' Adapter ha vari costruttori.
         *  Qui uso quello che collega al Context "this" una risorsa,
         *  prende poi una TextView R.layout.list_item,
         *  e infine prende l' array di elementi. */
        String[] list_countries = getResources().getStringArray(R.array.list_countries); 
        setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, list_countries));
        OnItemClickListener listener = new OnItemClickListener() {
        	@Override
        	public void onItemClick(AdapterView<?> parent, View view, int position,
        			long id) {
        		TextView tv = (TextView) view;
        		Toast.makeText(getApplicationContext(), tv.getText(), Toast.LENGTH_LONG).show();
        	}
		};
        ListView lv = getListView();
        lv.setTextFilterEnabled(true);
        lv.setOnItemClickListener(listener);
    }
}

Per quanto riguarda i vari “import”, mi lascio aiutare da Eclipse.