views:

1577

answers:

2

Hi!

I'm developing an simple application on Android, where some items are shown on a list. The user may click on one, taking him to a further activity. Basics...

But my OnItemClickListener does not get called! I've found this very similar question, but the solution (disallow the list item view to get focus) does not work for me. However, a long click gets catched - my OnItemLongClickListener gets called. Please have a look at the following code and try it yourself. This is a simplified version of my code, showing the buggy behavior. Btw: I'm using Andriod SDK 2.0 with Eclipse 3.5.1.

package de.sacherkhoudari.listtest;

import java.util.LinkedList;
import java.util.List;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;

public class ListTest extends ListActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final List<ListEntry> lEntries = new LinkedList<ListEntry>();
        for ( int ii = 0; ii < 10; ii++ )
            lEntries.add( new ListEntry( "Entry " + ii ) );

        setListAdapter( new ArrayAdapter<ListEntry>( this, R.layout.list_item, lEntries ) );

        ListView lv = getListView();
        lv.setTextFilterEnabled(true);

        lv.setOnItemClickListener( new OnItemClickListener() {
            public void onItemClick( AdapterView<?> parent, View view, int position, long id ) {
                Toast.makeText( ListTest.this, "ItemClick at item " + lEntries.get(position).toString(), Toast.LENGTH_LONG ).show();
            }
        });

        lv.setOnItemLongClickListener( new OnItemLongClickListener() {
            public boolean onItemLongClick( AdapterView<?> parent, View view, int position, long id ) {
                Toast.makeText( ListTest.this, "ItemLongClick at item " + lEntries.get(position).toString(), Toast.LENGTH_LONG ).show();
                return false;
            }
        });

        setContentView(lv);
    }
}

class ListEntry {
    private String name;

    public ListEntry( String s ) {
        name = s;
    }

    public String toString() {
        return name;
    }
}

So far the Java code... here comes the layout list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="10dp"
    android:textSize="16sp"
    android:focusable="false" >
</TextView>

Note: android:focusable="false" has no effect.

Thanks!
Sacher

+1  A: 

Move your setContentView(lv); right after retrieving the ListView

ListView lv = getListView();
setContentView(lv);
lv.setTextFilterEnabled(true);
ccheneson
Perfect, that worked! Thanks! But why? What changes when I call `setContentView()` before adding the listeners?
craesh
TBH, I dont know. I guess that events are linked to the activity and not the view itself (this is just a guess) - Please accept the answer if you think it's the right one
ccheneson
Apparently the above comment is not right - From http://developer.android.com/intl/de/guide/topics/ui/ui-events.html, "An event listener is an interface in the View class that contains a single callback method. These methods will be called by the Android framework when the View to which the listener has been registered is triggered by user interaction with the item in the UI." - Still trying to find some answers
ccheneson
Maybe... but very strange... in OOP, it should make no difference, if you first register an object and then change it's state, or first set it's state and then pass it's reference to your framework... But anyway, thanks for your answer. Of course you get it accepted :)
craesh
Since you can pass a XML layout to setContentView, onCreate will inflate it and add the UI to the view hierarchy. Maybe setting the view before associating any event is better for a common workflow (See the different setContentView signatures), again this is just my interpretation :)
ccheneson
+2  A: 

When you add layouts with setContentView, views within that layouts get freshly instanciated. The ListActivity has a very simple Layout by default (read about it here), even if you don't add your own layout. So basically in your first example:

  • First you add a listener to the default ListView within the ListActivity
  • Then you throw that ListView away by using setContentView to instanciate a new layout with a new ListView
  • Then you never register a new listener to the new ListView from your new layout.

Whereas when you pull setContentView up in your code you only ever work with your own ListView and everything works as expected.

rflexor
Interesting - What I dont understand is if setContentView creates new instances (in the first example), how come the ListView shows list items? Shouldnt it be a empty list?
ccheneson
From the doc "ListActivity hosts a ListView object that can be bound to different data sources" . Since setAdapter is called on (this), when it's instantiate, I guess that's why items are kept.
ccheneson
Thanks for your explanation! Is this a general rule? Do I always have to first instantiate my view through `setContentView()` and then set all listeners and other settings? I cannot find anything in the official docs about this. Do you know any further resources? Are there other pitfalls?
craesh
Yep, this is a general rule when using layouts on normal activities and starting the activity anew. You can try yourself: If you call findViewById before ever setting the view in onCreate() you'll get a null-reference back. The exception are Activities like the ListActivity that come with preinstanciated Views. The findViewById-javadoc (http://developer.android.com/reference/android/app/Activity.html#findViewById%28int%29) talks about "XML that was processed in onCreate(Bundle)" which imho can only mean the setContentView() in onCreate.
rflexor
Great! Thanks! :)
craesh