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

satya - Wednesday, September 28, 2011 1:29:41 PM

is there a list control with option buttons in Android

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

satya - Thursday, September 29, 2011 2:44:36 PM

Start with a list activity perhaps

Start with a list activity perhaps

satya - Thursday, September 29, 2011 10:36:23 PM

A sample list activity


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

satya - Thursday, September 29, 2011 10:39:59 PM

a useful ref to arrayadapter

a useful ref to arrayadapter

satya - Thursday, September 29, 2011 11:17:18 PM

Looks like there is a list view tutorial

Looks like there is a list view tutorial

satya - Friday, September 30, 2011 8:55:53 AM

android button clicks in a list view

android button clicks in a list view

Search for: android button clicks in a list view

satya - Friday, September 30, 2011 9:00:59 AM

setItemsCanFocus

setItemsCanFocus

Search for: setItemsCanFocus

satya - Friday, September 30, 2011 9:23:48 AM

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

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

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

satya - Friday, September 30, 2011 9:43:10 AM

Adapter ViewHolder android

Adapter ViewHolder android

Search for: Adapter ViewHolder android

satya - Friday, September 30, 2011 10:28:21 AM

what is a static java class

what is a static java class

Search for: what is a static java class

satya - Friday, September 30, 2011 10:37:36 AM

Apparently it is a member class that can be referenced from outside

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.

satya - Friday, September 30, 2011 10:37:57 AM

why ViewHolder findViewById performance

why ViewHolder findViewById performance

Search for: why ViewHolder findViewById performance

satya - Friday, September 30, 2011 10:59:00 AM

Access to parent variable is allowed in a member class


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

satya - Friday, September 30, 2011 11:00:06 AM

Not so for a static class


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

satya - Friday, September 30, 2011 11:57:50 AM

Say I want a custom list row: one text two buttons


<?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>

satya - Friday, September 30, 2011 11:58:44 AM

An unsuspecting attempt


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

satya - Friday, September 30, 2011 12:01:52 PM

what I was hoping for....

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

satya - Friday, September 30, 2011 12:07:26 PM

So a specialized array adapter like this will work


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());
    }
}

satya - Friday, September 30, 2011 12:09:11 PM

Tell me a bit about the getView() method

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.

satya - Friday, September 30, 2011 12:10:50 PM

Role of viewholder

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.

satya - Friday, September 30, 2011 1:41:32 PM

android listactivity emtpy view

android listactivity emtpy view

Search for: android listactivity emtpy view

satya - Friday, September 30, 2011 1:52:40 PM

On a listview you can set an 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());

    }
...
}

satya - Friday, September 30, 2011 2:03:17 PM

An array adapter can accept lists in addition to arrays

An array adapter can accept lists in addition to arrays

satya - Friday, September 30, 2011 6:11:01 PM

Read this on setemptyview programmatically

Read this on setemptyview programmatically

satya - Friday, September 30, 2011 6:17:04 PM

As the poster has indicated you have to do the following


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.

satya - 3/12/2013 11:00:46 AM

API reference for ArrayAdapter

API reference for ArrayAdapter

satya - 3/12/2013 11:09:56 AM

Behavior of an array adapter


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()

satya - 3/12/2013 11:10:56 AM

Now the following code should make sense


String[] listItems;

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

satya - 3/12/2013 2:45:43 PM

Here is more complete code on how to set an empty view


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

satya - 3/12/2013 2:46:32 PM

Here is that empty layout


<?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>

satya - 4/1/2013 3:00:32 PM

Here is API document for ListActivity

Here is API document for ListActivity

satya - 4/1/2013 3:02:53 PM

The exmples above did not specify a layout for the list activity

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.

satya - 4/1/2013 3:03:59 PM

Here is an example of a custom layout from the docs


<?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.

satya - 4/2/2013 9:18:36 AM

An array adapter has a number of constructors


//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

satya - 4/2/2013 9:20:16 AM

In variation1 above you typically use the layout id from the android.R.layout.* namespace

Example


android.R.layout.simple_list....

satya - 12/2/2014, 11:02:36 AM

Here is another note using setContentView

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)

satya - 12/2/2014, 11:04:34 AM

Here is the API for ListView

Here is the API for ListView

satya - 12/2/2014, 11:06:05 AM

Here is document on how to best use ListView

Here is document on how to best use ListView

satya - 12/2/2014, 12:43:48 PM

Here is a starting point for a list view


<?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>

satya - 12/2/2014, 12:44:35 PM

You can load this like this


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

satya - 12/2/2014, 12:49:19 PM

You can substitute a progress bar like this for the empty node


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

satya - 12/2/2014, 12:52:37 PM

Here is a basic signature for an activity implementing a loader manager


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

satya - 12/2/2014, 12:56:42 PM

Having a null cursor

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.

satya - 12/3/2014, 10:54:19 AM

See this note on how to use loaders with list views

See this note on how to use loaders with list views

satya - 12/3/2014, 4:30:25 PM

Here is how you can implement search really fast

Here is how you can implement search really fast

satya - 12/3/2014, 4:30:41 PM

It looks like this


@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;
    }

satya - 12/3/2014, 4:31:11 PM

You just have to implement the onQueryTextListener


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

satya - 12/3/2014, 4:46:34 PM

Here is how a modern activity (minus fragments may look like)


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