Рано или поздно приходит момент, когда набор стандартных виджетов становится тесен. Хочется добавить к базовым возможностям контролов дополнительные опции, способные обогатить интерфейс и улучшить пресловутый UX(User experience). Все что необходимо для расширения функциональности виджетов – немного покопаться в документации.
В данном посте описываются основы создания вашего собственного расширенного ListView. Следуя описанному здесь приему вы легко сможете добавлять к определенным строкам картинки, создавать сложные лайауты и отдельные обработчики событий для разных элементов ListView.
Ключевых момента тут два – создание собственного адаптера и использование его в активити, расширяющей обычную ListActivity.
I Этап
1. На основе ArrayAdapter создаем свой собственный объект – My_Custom_Adapter – он будет хранить все возможные типы строк в нашем ListView (например строка с текстом и строка, где кроме текста есть какая-нибудь иконка).
2. Для хранения всех элементов – all_items в сниппете – можно использовать любой класс-контейнер (в зависимости от ваших целей) – например ArrayList. Продумайте какой-тип объектов лучше подходит для ваших целей. В нашем примере – достаточно использовать обычные строки для заголовков. Для простоты определения типа элементов ArrayList создадим TreeSet, в который будем заносить индексы элементов второго типа.
3. Переопределяем для него функцию getView() – которая в зависимости от типа строчки возвращает view, соответствующее типу строки.
4. Переопределяем функцию getItemViewType() – она возвращает тип элемента по его индексу (а все индексы элементов второго типа мы храним в TreeSet’e).
5. Определим функции для добавления строк в наш ListView обоих типов – add_Item_Type_1(), add_Item_Type_2() – в любом случае, элемент добавляется в общий массив ArrayList, но если мы добавляем элемент второго типа – его индекс заносится в контейнер типа TreeSet. Важно после любого изменения в ArrayList вызывать метод notifyDataSetChanged() – для обновления картинки на экране телефона.
6. Набор методов для обновления адаптера при изменении информации (например при пересчете каких характеристик, обновлении данных с сервера и т.п.) – удобно объединить в одну функцию – Clear_Data().
Шаблонный сниппет для создания адаптера для CustomListView.
private class My_Custom_Adapter extends ArrayAdapter
{
private LayoutInflater my_Inflater;
private static final int TYPE_1 = 0;
private static final int TYPE_2 = 1;
private TreeSet Second_Items_Set = new TreeSet();
private ArrayList My_Custom_Object all_items;
public My_Custom_Adapter(Context context, int textViewResourceId, ArrayList My_Custom_Object items)
{
super(context, textViewResourceId, items);
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.all_items = items;
}
public View getView(int position, View convertView, ViewGroup parent)
{
int type = getItemViewType(position);
View v = convertView;
switch (type)
{
case TYPE_1:
v = mInflater.inflate(R.layout.base_row_for_first_type, null);
break;
case TYPE_2:
v = mInflater.inflate(R.layout.base_row_for_second_type, null);
break;
default:
// todo: deal with this situation
break;
}
My_Custom_Object cur_element = all_items.get(position);
switch (type)
{
case TYPE_1:
if (cur_element != null)
{
// TODO: According to object's data
// fill layout with appropriate data:
// specified picture, text, etc
}
break;
case TYPE_2:
if (cur_element != null)
{
// TODO: According to object's data
// fill layout with appropriate data:
// specified picture, text, etc
}
break;
default:
// todo: deal with this situation
break;
}
return v;
}
public void add_Item_Type_1(final String item_name)
{
item_type_1_list.add(item_name);
notifyDataSetChanged();
}
public void add_Item_Type_2(final String item_name)
{
item_type_2_list.add(item_name);
Second_Items_Set.add(all_items.size()-1);
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position)
{
return Second_Items_Set.contains(position) ? TYPE_2 : TYPE_1;
}
public void Clear_Data()
{
Second_Items_Set.clear();
all_items.clear();
clear();
}
}
II Этап
Cоздаем лайауты для различных типов строк, а также соответствующим образом их инициируем данными в методе getView() нашего адаптера.
Ссылки на функции, которые должны вызываться при нажатии – можно прописать прямо в xml-файлах разметки, заполнив поле onClick (в сниппете это – put_name_of_your_function_here), установив значение clickable в true, или в коде – как вам удобней.
<!--?xml version="1.0" encoding="utf-8"?-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/custom_row_item"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:onClick="put_name_of_your_function_here"
>
<!-- PUT Your Markup here -->
</LinearLayout>
III Этап
Для лайаута самой активити используем типовый лайаут для ListView:
<!--?xml version="1.0" encoding="utf-8"?-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/custom_list_view"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
>
<ListView
android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>
На основе ListActivity создаем свою собственную активити, в которую добавляем созданным нами адаптер.
Создаем метод который будет устанавливать адаптер для нашей активити – Prepare_Adapter(), а также метод Update_Activity_By_Some_Rule() который будет обновлять данные в нем по некоторым вашим правилам – например подгружая соответствующие строчки для очередной записи, отображаемой в ListView.
public class My_List_Activity extends ListActivity
{
private ArrayList all_objects = null;
private My_Custom_Adapter Cur_Adapter;
@Override
public void onCreate(Bundle savedInstanceState)
{
setContentView(R.layout.custom_list_view);
super.onCreate(savedInstanceState);
Prepare_Adapter();
}
private void Prepare_Adapter()
{
if (Cur_Adapter == null)
{
this.Cur_Adapter = new My_Custom_Adapter(this, R.layout.base_row_for_first_type, all_objects);
}
else
{
Cur_Adapter.Clear_Data();
}
setListAdapter(this.Cur_Adapter);
}
private void Update_Activity_By_Some_Rule()
{
Cur_Adapter.add_Item_Type_1("Test item 1");
Cur_Adapter.add_Item_Type_2("Test item 2");
}
}
Android и Custom ListView еще ссылки на эту тему:
http://www.softwarepassion.com/android-series-custom-listview-items-and-adapters/
http://www.pocketmagic.net/?p=1678
http://www.androidsnippets.com/clickable-listview-items