/*
* Copyright (C) 2011 Virginia Tech Department of Computer Science
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sofia.app;
import sofia.app.internal.EventBinder;
import sofia.widget.ListView;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import java.util.Collection;
import java.util.List;
// -------------------------------------------------------------------------
/**
* <p>
* {@code ListScreen} is a subclass of screen that provides a built-in
* {@link ListView} and convenience methods for manipulating the list directly
* in the screen class instead of having to call {@link #getListView()} for
* every operation.
* </p><p>
* The {@code ListScreen} class is generic, so users who extend it should
* include the type of the items that will be stored in the list so that
* methods like {@code add} and {@code remove} will have the correct types. For
* example,
* </p>
* <pre>
* public class MyListScreen extends ListScreen<MyObject> { ...</pre>
* <p>
* Please see the documentation for {@link ListView} for more information on
* how arbitrary objects are displayed in the list.
* </p><p>
* When you subclass {@code ListScreen}, by default it will create a new
* {@code ListView} that occupies the entire width and height of the screen.
* If this is not what you want (for example, if you want to have a
* {@code ListView} alongside other widgets but still retain the convenience
* of methods like {@link #add(E)} directly on the screen), then place an
* instance of {@code ListView} in your layout file with the ID
* {@code listView}. Then the {@code ListScreen} will use that view for all
* of its other methods instead of creating its own.
* </p><p>
* Note to developers coming from the traditional Android API: though similar,
* {@code ListScreen} is not a subclass of {@code ListActivity}, and it does
* not have exactly the same functionality or interface.
* </p>
*
* @param <E> the type of items that will be stored in the list
*
* @author Tony Allevato
*/
public abstract class ListScreen<E> extends Screen
{
//~ Fields ................................................................
private ListView<E> listView;
private View emptyView;
//~ Methods ...............................................................
// ----------------------------------------------------------
@SuppressWarnings("unchecked")
@Override
protected void afterLayoutInflated(boolean inflated)
{
boolean hasListView = false;
boolean hasEmptyView = false;
if (inflated)
{
int listViewId = getResources().getIdentifier(
"listView", "id", getPackageName());
if (listViewId != 0)
{
listView = (ListView<E>) findViewById(listViewId);
hasListView = true;
}
int emptyViewId = getResources().getIdentifier(
"emptyView", "id", getPackageName());
if (emptyViewId != 0)
{
emptyView = findViewById(emptyViewId);
hasEmptyView = true;
}
}
if (!hasListView)
{
listView = createListView(this);
listView.setClickable(true);
new EventBinder(this).bindEvents(listView, null);
setContentView(listView);
listView.requestFocus();
}
if (!hasEmptyView)
{
emptyView = createEmptyView(this);
emptyView.setVisibility(View.GONE);
addContentView(emptyView,
new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
listView.setEmptyView(emptyView);
}
}
// ----------------------------------------------------------
/**
* This factory method is used to create the {@link ListView}
* that will be contained by this screen. It is provided for
* subclass extensibility, in case a subclass of {@code ListScreen} wants
* to use a more specialized {@code ListView} instance.
*
* @param parent The screen that will contain the view (e.g., "this")
* @return A new {@code ListView} object to use for this screen.
*/
protected ListView<E> createListView(ListScreen<E> parent)
{
return new ListView<E>(parent);
}
// ----------------------------------------------------------
/**
* <p>
* This factory method is used to create the {@link View} that will be
* displayed when the list is empty. It is provided for subclass
* extensibility, in case a subclass of {@code ListScreen} wants to use a
* more specialized view.
* </p><p>
* The default implementation of this method creates a {@link TextView}
* with {@code textAppearanceMedium}, text horizontally and vertically
* centered, and a padding of 10 pixels.
* </p>
*
* @param parent The screen that will contain the view (e.g., "this")
* @return A new {@code View} object to display when the list is empty.
*/
protected TextView createEmptyView(ListScreen<E> parent)
{
TextView view = new TextView(parent);
view.setTextAppearance(this, android.R.style.TextAppearance_Medium);
view.setGravity(Gravity.CENTER);
view.setPadding(10, 10, 10, 10);
return view;
}
// ----------------------------------------------------------
/**
* Overridden to refresh the list view whenever the screen is about to be
* presented to the user, in case the underlying data model has changed due
* to actions on another screen.
*/
@Override
protected void onResume()
{
super.onResume();
refresh();
}
// ----------------------------------------------------------
/**
* Gets the {@link ListView} that holds all of the items on this screen.
*
* @return The {@link ListView} that holds all of the items on this
* screen.
*/
public final ListView<E> getListView()
{
return listView;
}
// ----------------------------------------------------------
/**
* Gets teh {@link View} that is displayed when there are no items in the
* list.
*
* @return The {@link View} that is displayed when there are no items in
* the list.
*/
public final View getEmptyView()
{
return emptyView;
}
// ----------------------------------------------------------
/**
* Gets the list of items that is managed by this list view. Changes made
* to the structure of the list returned by this method (that is, adding,
* removing, or replacing items) will be immediately reflected in the list
* view. You only need to explicitly {@link #refresh()} the list if you
* make a change to an element inside the list without directly modifying
* the list itself.
*
* @return the {@link List} of items managed by this list view
*/
public List<E> getList()
{
return listView.getList();
}
// ----------------------------------------------------------
/**
* Adds an item to the list view.
*
* @param item the item to add to the list view
* @return true if the item could be added, or false if it could not
*/
public boolean add(E item)
{
return listView.add(item);
}
// ----------------------------------------------------------
/**
* Inserts an item into the list view at the specified index.
*
* @param index the index where the new item should be inserted
* @param item the item to add to the list view
* @return true if the item could be added, or false if it could not
*/
public void add(int index, E item)
{
listView.add(index, item);
}
// ----------------------------------------------------------
/**
* Adds the items in the specified collection to the list view.
*
* @param collection the items to add to the list view
* @return rue if the items could be added, or false if they could not
*/
public boolean addAll(Collection<? extends E> collection)
{
return listView.addAll(collection);
}
// ----------------------------------------------------------
/**
* Inserts the items in the specified collection into the list view at the
* specified index.
*
* @param index the index where the new items should be inserted
* @param collection the items to add to the list view
* @return true if the items could be added, or false if they could not
*/
public boolean addAll(int index, Collection<? extends E> collection)
{
return listView.addAll(index, collection);
}
// ----------------------------------------------------------
/**
* Removes all items from the list view.
*/
public void clear()
{
listView.clear();
}
// ----------------------------------------------------------
/**
* Gets the element at the specified index from the list view.
*
* @param index the index of the item to retrieve
* @return the item at the specified index
*/
public E get(int index)
{
return listView.get(index);
}
// ----------------------------------------------------------
/**
* Removes the item at the specified index from the list view.
*
* @param index the index of the item to be removed
* @return the item that was removed
*/
public E remove(int index)
{
return listView.remove(index);
}
// ----------------------------------------------------------
/**
* Removes the specified item from the list view.
*
* @param item the item to remove from the list view
* @return true if the item was found and removed, or false if it was not
*/
public boolean remove(E item)
{
return listView.remove(item);
}
// ----------------------------------------------------------
/**
* Replaces the element at the specified index from the list view with
* another item.
*
* @param index the index of the item to retrieve
* @param item the item to put into the list
* @return the item previously at the specified index
*/
public E set(int index, E item)
{
return listView.set(index, item);
}
// ----------------------------------------------------------
/**
* Gets the currently selected item in the list view.
*
* @return the currently selected item in the list view, or null if there
* is no item selected
*/
public E getSelectedItem()
{
return listView.getSelectedItem();
}
// ----------------------------------------------------------
/**
* Refreshes the list view to update its contents from the list it manages.
* This method does not need to be called after methods like {@code add}
* or {@code remove} -- it only needs to be called if you change a property
* of one of the elements in the list (for example, by calling a setter)
* without modifying the structure of the list itself.
*/
public void refresh()
{
listView.refresh();
}
// ----------------------------------------------------------
/**
* <p>
* Sets an informational message that will be displayed on the screen when
* the list is empty.
* </p><p>
* It is only appropriate to call this method if the empty view is a
* {@link TextView} (or subclass of {@code TextView}). If the view is any
* other type, an exception will be thrown.
* </p>
*
* @param message the list
* @throws IllegalStateException if the empty view is not a
* {@code TextView} (or subclass)
*/
public void setEmptyMessage(String message)
{
if (emptyView instanceof TextView)
{
((TextView) emptyView).setText(message);
}
else
{
throw new IllegalStateException(
"You may only call setEmptyMessage if the ListScreen's "
+ "empty view is a TextView (or subclass of TextView).");
}
}
}