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
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
satya - Thursday, September 29, 2011 11:17:18 PM
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
satya - Friday, September 30, 2011 9:00:59 AM
setItemsCanFocus
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
satya - Friday, September 30, 2011 10:28:21 AM
what is a static java class
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
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
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
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: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
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:06:05 AM
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
satya - 12/3/2014, 4:30:25 PM
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