working with lists

You will explore here

overriding getView of adapters
How to make row elements clickable
How to use view holder pattern
static member classes
difference between 
  clicking a whole row
  and parts of a row.
Providing empty views to a list control

is there a list control with option buttons in Android

Search for: is there a list control with option buttons in Android

I want this control to show 1 to n buttons where x number of buttons are on the same row and the additional buttons are displayed in a follow up row activated by onclick, much like contats

Start with a list activity perhaps


public class WordListActivity 
extends ListActivity
{
    @Override     
    protected void onCreate(Bundle savedInstanceState)
    {         
    	super.onCreate(savedInstanceState);
    	//setContentView(android.R.layout.)
    	this.setListAdapter(getAdapter());
    }
    private ListAdapter getAdapter()
    {
    	  String[] listItems = new String[] {
    	        "Item 1", "Item 2", "Item 3",
    	        "Item 4", "Item 5", "Item 6",
    	  };
    	  
    	  ArrayAdapter<String> listItemAdapter = 
    		   new ArrayAdapter<String>(this
    		           ,android.R.layout.simple_list_item_1
    		           ,listItems);
    	  return listItemAdapter;
    }
    
}//eof-class

a useful ref to arrayadapter

Looks like there is a list view tutorial

android button clicks in a list view

Search for: android button clicks in a list view

setItemsCanFocus

Search for: setItemsCanFocus

is there a better ListView than one in the Android SDK?

Search for: is there a better ListView than one in the Android SDK?

Adapter ViewHolder android

Search for: Adapter ViewHolder android

what is a static java class

Search for: what is a static java class

Why? perhaps to save it from creating another file? Does it have access to parent object variables? probably not? it has no object context as it is static, much like a static function not having access to instance variables.

why ViewHolder findViewById performance

Search for: why ViewHolder findViewById performance


public class Parent
{
   int parentVariable=0;
   class MemberClass
   {
      int childVariable=10;
      public void test()
      {
          childVariable = 15;//obviously good
          parentVariable = 10; //good and nice.
      }
}

public class Parent
{
   int parentVariable=0;
   static class MemberClass
   {
      int childVariable=10;
      public void test()
      {
          childVariable = 15;//obviously good
          parentVariable = 10; //NOT ALLOWED
      }
   }//eof-inner-class
}//eof-outer-class

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >
<TextView  
   android:id="@+id/wordListRowId"
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:text=""
    android:layout_weight="1"
    />
<Button 
   android:id="@+id/wordListRowSlvBtnId"
   android:text="Slv" 
   android:layout_width="wrap_content" 
   android:layout_height="wrap_content"
   android:layout_weight="0"
   />
<Button 
   android:id="@+id/wordListRowDelBtnId"
    android:text="Del" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:layout_weight="0"
    />
</LinearLayout>

public class WordListActivity 
extends ListActivity
implements OnItemClickListener
{
    @Override     
    protected void onCreate(Bundle savedInstanceState)
    {         
        super.onCreate(savedInstanceState);
        //setContentView(android.R.layout.)
        this.setListAdapter(getAdapter());
        ListView lv = this.getListView();
        lv.setOnItemClickListener(this);
        lv.setItemsCanFocus(true);
    }
    private ListAdapter getAdapter()
    {
          String[] listItems = new String[] {
                "Item 1", "Item 2", "Item 3",
                "Item 4", "Item 5", "Item 6",
          };
          
          ArrayAdapter<String> listItemAdapter = 
               new ArrayAdapter<String>(this
                       ,R.layout.word_list_item_layout
                       ,R.id.wordListRowId
                       ,listItems);
          return listItemAdapter;
    }
    @Override
    public void onItemClick(AdapterView<?> listView, 
            View childView, int position, long rowId) 
    {
        if (childView.getId() == R.id.wordListRowDelBtnId)
        {
            Log.d("me", "Del button pressed");
        }
    }
    
}//eof-class

I will be able to click on the text or button 1 or button 2 and I get a single callback through onItemClick with a pointer to the child view where it is clicked.

Thats not what is happening.

Multiple problems.

Apparently the onitemcallback is meant for the whole row even if it has other buttons on it. It may even be disabled if there are focusable items on it.

most likely I will have to override the getView method of the array adapter and provide my own callbacks for each of the buttons and load the row id as a tag on that particular view call back

Hoping to post an example soon


public class WordListAdapter 
extends ArrayAdapter<String>
implements View.OnClickListener
{

    private static String tag = "WordListAdapter";
    String[] wordArray;
    Context ctx = null;
    LayoutInflater lif = null;
    public WordListAdapter(Context context, String[] inWordArray) 
    {
        super(context
           ,R.layout.word_list_item_layout
           ,R.id.wordListRowId
           ,inWordArray);
        wordArray = inWordArray;
        ctx = context;
      lif = LayoutInflater.from(ctx);
    }
    
    //This class saves references
    //to the buttons that are displayed
    //for each row
    //If there are 10 rows
    //then there are 10 instances of buttons
    //and 10 instances of view holder
    static class ViewHolder
    {
        public ViewHolder(View rowView, View.OnClickListener ocl)
        {
            delButton =
                (Button)rowView.findViewById(R.id.wordListRowDelBtnId);
            playButton =
                (Button)rowView.findViewById(R.id.wordListRowSlvBtnId);
            textView =
                (TextView)rowView.findViewById(R.id.wordListRowId);
            
            delButton.setOnClickListener(ocl);
            playButton.setOnClickListener(ocl);
        }
        public TextView textView;
        public Button delButton;
        public Button playButton;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) 
    {
        ViewHolder vh;
        View thisView;
        if (convertView == null)
        {
            //create the view
            View rowView =
                lif.inflate(R.layout.word_list_item_layout,null);
            vh = new ViewHolder(rowView,this);
            rowView.setTag(vh);
            thisView = rowView;
        }
        else
        {
            //populate the view
            vh = (ViewHolder)convertView.getTag();
            thisView = convertView;
        }
        //got a view holder
        //and a view.
        String curword = this.wordArray[position];
        vh.textView.setText(curword);
        vh.delButton.setTag(curword);
        vh.playButton.setTag(curword);
        return thisView;
    }
    @Override
    public void onClick(View v) 
    {
        Log.d(tag, "Click detected for word:" + v.getTag());
    }
}

Primary responsibility is to return a row view to be painted by the list view

the first time the view may be null. This indicates that you have to inflate your view and return it.

Other times you may be passed a recycled view. In this case you need to adjust your text and callback arguments and such.

Even when a recycled view comes along, you may want to save time by not doing subsequent findViewsById but instead cache those child/child/child views so that you can get to them directly. That is all ViewHolder is doing.

android listactivity emtpy view

Search for: android listactivity emtpy view


public class WordListActivity 
extends ListActivity
{
    @Override     
    protected void onCreate(Bundle savedInstanceState)
    {         
    	super.onCreate(savedInstanceState);
    	//setContentView(android.R.layout.)
    	this.setListAdapter(getAdapter());
    	ListView lv = this.getListView();
    	lv.setItemsCanFocus(true);

    	lv.setEmptyView(getAnEmptyView());

    }
...
}

An array adapter can accept lists in addition to arrays

Read this on setemptyview programmatically


View emptyView = getEmptyView();
emptyView.setVisibility(View.GONE);
((ViewGroup)(lv.getParent())).addView(emptyView);
lv.setEmptyView(emptyView);

It is probably because a list activity is assuming that in parallel to the list view there is an empty view that is hidden which will get visible. This little detail has escaped the docs.

API reference for ArrayAdapter


Provide a series of text views to the list activity. 
A resource id pointing to the text view is an input
There are prefabricated ids for these if you want to use them
The text for the text view will come from an array of objects
As long as the objects implement toString() method
If you want a complex view for each row then override getView()

String[] listItems;

ArrayAdapter<String> listItemAdapter = 
   new ArrayAdapter<String>(this
   ,android.R.layout.simple_list_item_1
   ,listItems);

public class WordListActivity 
extends ListActivity
{
    @Override     
    protected void onCreate(Bundle savedInstanceState)
    {         
       super.onCreate(savedInstanceState);
       //setContentView(android.R.layout.)
       this.setListAdapter(getAdapter1());
       ListView lv = this.getListView();
       lv.setItemsCanFocus(true);
       
       View emptyView = getEmptyView();
       emptyView.setVisibility(View.GONE);
       ((ViewGroup)(lv.getParent())).addView(emptyView);
       lv.setEmptyView(emptyView);
    }
    private ListAdapter getAdapter()
    {
         String[] listItems = new String[] {
               "Item 1", "Item 2", "Item 3",
               "Item 4", "Item 5", "Item 6",
         };
         
         return new WordListAdapter(this,listItems);
    }
    private ListAdapter getAdapter1()
    {
         List<Word> wordList = WordRepository.m_self.getWordList();
         return new WordListAdapter(this,wordList);
    }
    private View getEmptyView()
    {
       LayoutInflater lif = LayoutInflater.from(this);
       View v = lif.inflate(R.layout.word_list_empty_layout, null);
       return v;
    }
}//eof-class

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/empty"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >
<TextView  
   android:id="@+id/EmptyWordListTextViewId"
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:text="No Words available. Go Back and use a New word to create one."
    android:layout_weight="1"
    />
</LinearLayout>

Here is API document for ListActivity

because the examples assumed the whole page is taken by the list. An alternative is to use a custom layout with a place holder for the list control and have the independence to choose whatever controls we need outside of it. Here is an example from the link above.


<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:orientation="vertical"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:paddingLeft="8dp"
         android:paddingRight="8dp">

     <ListView android:id="@android:id/list"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:background="#00FF00"
               android:layout_weight="1"
               android:drawSelectorOnTop="false"/>

     <TextView android:id="@android:id/empty"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:background="#FF0000"
               android:text="No data"/>
 </LinearLayout>

now you can put buttons and other stuff around the list. for example you can create button to add a row to the list.


//variation1
context
a layout id which has a textview in it
list of objects that have toString() operations on it

//variation2
context
a layout id
a resource id of the text view inside that layout
list of objects

Example


android.R.layout.simple_list....

ListActivity has a default layout that consists of a single, full-screen list in the center of the screen. However, if you desire, you can customize the screen layout by setting your own view layout with setContentView() in onCreate(). To do this, your own view MUST contain a ListView object with the id "@android:id/list" (or list if it's in code)

Here is the API for ListView

Here is document on how to best use ListView


<?xml version="1.0" encoding="utf-8"?>
<!-- 
*********************************************
* test_loaders_activity_layout.xml
* corresponding activity: TestLoadersActicity.java 
* prefix: tla_ (Used for prefixing unique identifiers) 
* 
* Use: 
*    Demonstrate loading a cursor using loaders
* Structure:
*    Header message: text view (tla_header)
*    ListView (fixed)
*    Footer: text view (tla_footer)
*    Empty View (To show when the list is empty): ProgressBar
************************************************
--> 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:orientation="vertical"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:paddingLeft="8dp"
         android:paddingRight="8dp">

     <TextView android:id="@+id/tla_header"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:background="#FF0000"
               android:text="Sample Message"/>
     <!--  Uses a standard id needed by a list view -->
     <ListView android:id="@android:id/list"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:background="#00FF00"
               android:layout_weight="1"
               android:drawSelectorOnTop="false"/>

     <!--  Uses a standard id needed by a list view -->
     <!--  This can be a progress bar view -->
     <TextView android:id="@android:id/empty"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:background="#FF0000"
               android:text="No data, on Empty"/>
     
     <TextView android:id="@+id/tla_footer"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:background="#FF0000"
               android:text="No data, Footer"/>
     </LinearLayout>

public class TestLoadersActivity 
extends ListActivity
{
   @Override     
    protected void onCreate(Bundle savedInstanceState)  {         
       super.onCreate(savedInstanceState);
       this.setContentView(R.layout.test_loaders_activity_layout);
       
       this.setListAdapter(getAdapter());
    }
    
    private ListAdapter getAdapter() {
       // For the cursor adapter, specify which columns go into which views
        String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME};
        int[] toViews = {android.R.id.text1}; // The TextView in simple_list_item_1
       return new SimpleCursorAdapter(this, 
             android.R.layout.simple_list_item_1,
             null, //curosr 
             fromColumns, 
             toViews);
    }    
}//eof-class

<ProgressBar android:id="@android:id/empty"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:layout_gravity="center"
               android:indeterminate="true"/>

public class TestLoadersActivity 
extends ListActivity
implements LoaderManager.LoaderCallbacks<Cursor> 
{
   @Override     
    protected void onCreate(Bundle savedInstanceState)  {         
       super.onCreate(savedInstanceState);
       this.setContentView(R.layout.test_loaders_activity_layout);
       
       this.setListAdapter(getAdapter());
       
       //Initialzie a loader for an id of 0
        getLoaderManager().initLoader(0, null, this);
    }
    
    private ListAdapter getAdapter() {
       // For the cursor adapter, specify which columns go into which views
        String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME};
        int[] toViews = {android.R.id.text1}; // The TextView in simple_list_item_1
       return new SimpleCursorAdapter(this, 
             android.R.layout.simple_list_item_1,
             null, //curosr 
             fromColumns, 
             toViews);
    }

   public Loader<Cursor> onCreateLoader(int id, Bundle args) {
      // TODO Auto-generated method stub
      return null;
   }

   public void onLoadFinished(Loader<Cursor> arg0, Cursor arg1) {
      // TODO Auto-generated method stub
      
   }

   public void onLoaderReset(Loader<Cursor> arg0) {
      // TODO Auto-generated method stub
      
   }   
}//eof-class

will trigger the listview to show the specified empty view. This empty view can be explicitly set or identied with an id "android:id/empty"

this empty view can be an embedded indeterminate progress bar as well, which will be replaced as soon as the null cursor is swapped with a valid one.

See this note on how to use loaders with list views

Here is how you can implement search really fast


@Override 
    public boolean onCreateOptionsMenu(Menu menu) {
        // Place an action bar item for searching.
        MenuItem item = menu.add("Search");
        item.setIcon(android.R.drawable.ic_menu_search);
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        SearchView sv = new SearchView(this);
        sv.setOnQueryTextListener(this);
        item.setActionView(sv);
        return true;
    }
    
    public boolean onQueryTextChange(String newText) {
        // Called when the action bar search text has changed.  Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }

    @Override 
    public boolean onQueryTextSubmit(String query) {
        // Don't care about this.
        return true;
    }

public class TestLoadersActivity 
extends MonitoredListActivity
implements LoaderManager.LoaderCallbacks<Cursor>, OnQueryTextListener

Uses loaders for cursors
Deals with rotation due to loaders
Deals with data changes to the cursor
Does a search
Updates the loader based on new search

public class TestLoadersActivity 
extends MonitoredListActivity
implements LoaderManager.LoaderCallbacks<Cursor>, OnQueryTextListener 
{
   private static final String tag = "TestLoadersActivity";
   
    // This is the Adapter being used to display the list's data
   //Initialized in onCreate and set on the list
   //You can use it later to swap cursors on this adapter
   //You get a new one when rotation occurs
    SimpleCursorAdapter mAdapter;
    
    //Search filter
    String mCurFilter;    
   
    // These are the Contacts rows that we will retrieve
    static final String[] PROJECTION = new String[] {ContactsContract.Data._ID,
            ContactsContract.Data.DISPLAY_NAME};

    // This is the select criteria
    static final String SELECTION = "((" + 
            ContactsContract.Data.DISPLAY_NAME + " NOTNULL) AND (" +
            ContactsContract.Data.DISPLAY_NAME + " != '' ))";
    
    public TestLoadersActivity()  {
       super(tag);
    }
    @Override     
    protected void onCreate(Bundle savedInstanceState)  {         
       super.onCreate(savedInstanceState);
       this.setContentView(R.layout.test_loaders_activity_layout);
       
       this.mAdapter = createEmptyAdapter();
       this.setListAdapter(mAdapter);
       
       //Initialzie a loader for an id of 0
        getLoaderManager().initLoader(0, null, this);
    }
    private SimpleCursorAdapter createEmptyAdapter() {
       // For the cursor adapter, specify which columns go into which views
        String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME};
        int[] toViews = {android.R.id.text1}; // The TextView in simple_list_item_1
       return new SimpleCursorAdapter(this, 
             android.R.layout.simple_list_item_1,
             null, //curosr 
             fromColumns, 
             toViews);
    }
    private ListAdapter getCurrentAdapter() {
       return this.getListAdapter();
    }
   public Loader<Cursor> onCreateLoader(int id, Bundle args) {
      Log.d(tag,"onCreateLoader for loader id:" + id);
      Uri baseUri;
      if (mCurFilter != null) {
          baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                 Uri.encode(mCurFilter));
      } else {
          baseUri = Contacts.CONTENT_URI;
      }      
      String[] selectionArgs = null;
      String sortOrder = null;
      return new CursorLoader(this, baseUri,
            PROJECTION, SELECTION, selectionArgs, sortOrder);
   }
   public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
       Log.d(tag,"onLoadFinished for loader id:" + loader.getId());
        this.mAdapter.swapCursor(cursor);
   }
   public void onLoaderReset(Loader<Cursor> loader) {
      Log.d(tag,"onLoadFinished for loader id:" + loader.getId());
      this.mAdapter.swapCursor(null);
   }
    @Override 
    public boolean onCreateOptionsMenu(Menu menu) {
        // Place an action bar item for searching.
        MenuItem item = menu.add("Search");
        item.setIcon(android.R.drawable.ic_menu_search);
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        SearchView sv = new SearchView(this);
        sv.setOnQueryTextListener(this);
        item.setActionView(sv);
        return true;
    }
    public boolean onQueryTextChange(String newText) {
        // Called when the action bar search text has changed.  Update
        // the search filter, and restart the loader to do a new query
        // with this filter.
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }

    @Override 
    public boolean onQueryTextSubmit(String query) {
        return true;
    }
}//eof-class