Списки, разбитые на разделы, довольно часто встречаются как в системных приложениях, так и в сторонних.
Но, как ни странно, платформа не предлагает способов их реализации «из коробки», в отличие от iOS.
Я знаю два подхода к реализации заголовков списков:
- В макете каждого элемента списка прописывается заголовок, видимость которого будет определяться условием «является ли этот элемент первым в разделеЭ»
- Заголовок списка создается отдельно и в методе Адаптер#getView есть выбор: создать элемент типа «заголовок», либо элемент типа «обычная линия»
Но это влечет за собой более «тяжелый» интерфейс с точки зрения производительности.
Второй немного сложнее в реализации, но немного быстрее и, что самое главное, позволяет использовать макеты системы .
В этой статье используется второй подход.
Базовый класс адаптера
Базовый адаптер берет на себя ответственность за определение типа строки списка и взамен запрашивает у своих преемников возможность создавать заголовки и строки данных:Далее при изменении данных в списке (при вызове уведомитьDataSetChanged ), мы подсчитываем общее количество строк в списке, кэшируем количество элементов в каждом разделе, количество разных типов строк списка и ассоциацию этих типов с разделами:protected abstract int getSectionsCount( ); protected abstract int getRowsCountInSection( int section ); protected abstract View getHeaderView( int section, View convertView, ViewGroup parent, LayoutInflater inflater ); protected abstract View getItemView( int section, int row, View convertView, ViewGroup parent, LayoutInflater inflater );
private void setupAdapter( ) {
int sectionsCount = getSectionsCount();
_rowsCountsInSections = new int [sectionsCount];
_viewTypes = new int [sectionsCount];
_totalRowsCount = sectionsCount;
Set<Integer> viewTypesSet = new HashSet<Integer>();
for (int i = 0; i < sectionsCount; i++) {
_rowsCountsInSections[i] = getRowsCountInSection(i);
_totalRowsCount += _rowsCountsInSections[i];
_viewTypes[i] = getViewTypeForSection(i);
viewTypesSet.add(_viewTypes[i]);
}
viewTypesSet.add(VIEW_TYPE_HEADER);
_viewTypesCount = viewTypesSet.size();
}
На основе собранной информации мы легко выберем, что нам нужно создать в getView:
@Override
final public View getView( int position, View convertView, ViewGroup parent ) {
int section = getItemSection(position);
if (isItemHeader(position))
return getHeaderView(section, convertView, parent, _inflater);
int rowInSection = getItemRowInSection(position);
return getItemView(section, rowInSection, convertView, parent, _inflater);
}
Мы определяем, является ли строка заголовком или элементом раздела, следующим образом:
private boolean isItemHeader( int position ) {
int sum = 0;
for (int i = 0; i < _rowsCountsInSections.length && sum <= position; i++) {
if (sum == position)
return true;
sum += _rowsCountsInSections[i] + 1;
}
return false;
}
private int getItemSection( int position ) {
int sum = 0;
int section = 0;
while (sum <= position && section < _rowsCountsInSections.length)
sum += _rowsCountsInSections[section++] + 1;
return section - 1;
}
private int getItemRowInSection( int position ) {
int section = getItemSection(position);
int sum = 0;
for (int i = 0; i < section; i++)
sum += _rowsCountsInSections[i] + 1;
return position - sum - 1;
}
В принципе, способы получения номера раздела и индекса внутри раздела можно объединить в один; разделение оставлено для ясности.
Весь базовый класс: abstract public class BaseSectionedListAdapter extends BaseAdapter {
private static int VIEW_TYPE_HEADER = 0;
protected static int VIEW_TYPE_DATA = 1;
final private LayoutInflater _inflater;
private int _totalRowsCount = -1;
private int [] _rowsCountsInSections;
private int [] _viewTypes;
private int _viewTypesCount;
public BaseSectionedListAdapter( Context context ) {
_inflater = LayoutInflater.from(context);
}
@Override
final public int getCount( ) {
if (_totalRowsCount == -1)
setupAdapter();
return _totalRowsCount;
}
@Override
final public Object getItem( int position ) {
return null;
}
@Override
final public long getItemId( int position ) {
return position;
}
@Override
final public View getView( int position, View convertView, ViewGroup parent ) {
int section = getItemSection(position);
if (isItemHeader(position))
return getHeaderView(section, convertView, parent, _inflater);
int rowInSection = getItemRowInSection(position);
return getItemView(section, rowInSection, convertView, parent, _inflater);
}
@Override
public int getItemViewType( int position ) {
if (isItemHeader(position))
return VIEW_TYPE_HEADER;
int section = getItemSection(position);
return _viewTypes[section];
}
@Override
public int getViewTypeCount( ) {
return _viewTypesCount;
}
@Override
public boolean isEnabled( int position ) {
return !isItemHeader(position);
}
@Override
final public boolean areAllItemsEnabled( ) {
return false;
}
@Override
final public void notifyDataSetChanged( ) {
super.notifyDataSetChanged();
setupAdapter();
}
@Override
public boolean hasStableIds( ) {
return true;
}
private boolean isItemHeader( int position ) {
int sum = 0;
for (int i = 0; i < _rowsCountsInSections.length && sum <= position; i++) {
if (sum == position)
return true;
sum += _rowsCountsInSections[i] + 1;
}
return false;
}
private int getItemSection( int position ) {
int sum = 0;
int section = 0;
while (sum <= position && section < _rowsCountsInSections.length)
sum += _rowsCountsInSections[section++] + 1;
return section - 1;
}
private int getItemRowInSection( int position ) {
int section = getItemSection(position);
int sum = 0;
for (int i = 0; i < section; i++)
sum += _rowsCountsInSections[i] + 1;
return position - sum - 1;
}
private void setupAdapter( ) {
int sectionsCount = getSectionsCount();
_rowsCountsInSections = new int [sectionsCount];
_viewTypes = new int [sectionsCount];
_totalRowsCount = sectionsCount;
Set<Integer> viewTypesSet = new HashSet<Integer>();
for (int i = 0; i < sectionsCount; i++) {
_rowsCountsInSections[i] = getRowsCountInSection(i);
_totalRowsCount += _rowsCountsInSections[i];
_viewTypes[i] = getViewTypeForSection(i);
viewTypesSet.add(_viewTypes[i]);
}
viewTypesSet.add(VIEW_TYPE_HEADER);
_viewTypesCount = viewTypesSet.size();
}
protected int getViewTypeForSection( int section ) {
return VIEW_TYPE_DATA;
}
protected abstract int getSectionsCount( );
protected abstract int getRowsCountInSection( int section );
protected abstract View getHeaderView( int section, View convertView,
ViewGroup parent, LayoutInflater inflater );
protected abstract View getItemView( int section, int row, View convertView,
ViewGroup parent, LayoutInflater inflater );
}
Пример использования
Простой пример: мы предоставляем на вход список «разделов», рисуем все данные раздела в одном стиле.
В разделе представлены имя заголовка и набор данных: final public class SimpleSection {
final private String _title;
final private List<?> _data;
public SimpleSection( String title, List<?> data ) {
_title = title;
_data = data;
}
public String getTitle( ) {
return _title;
}
public List<?> getData( ) {
return _data;
}
}
Для шапки используется простейший макет, состоящий из одного TextView: <TextView
xmlns:android="http://schemas.android.com/apk/res/android "
android:id="@android:id/text1 "
android:layout_width="match_parent "
android:layout_height="30dp "
android:textAppearance="Эandroid:attr/textAppearanceSmallInverse "
android:gravity="center_vertical "
android:paddingLeft="6dip "
android:background="@android:color/black "
/>
Простой адаптер целиком: final public class SimpleSectionedListAdapter extends BaseSectionedListAdapter {
final private List<SimpleSection> _sections;
public SimpleSectionedListAdapter( Context context, List<SimpleSection> sections ) {
super(context);
_sections = sections;
}
@Override
protected int getSectionsCount( ) {
return _sections.size();
}
@Override
protected int getRowsCountInSection( int section ) {
return _sections.get(section).
getData().
size(); } @Override protected View getHeaderView( int section, View convertView, ViewGroup parent, LayoutInflater inflater ) { if (convertView == null) convertView = inflater.inflate(R.layout.list_header, parent, false); TextView text = (TextView)convertView; text.setText(_sections.get(section).
getTitle()); return convertView; } protected Object getItemInSection( int section, int row ) { return _sections.get(section).
getData().
get(row);
}
@Override
protected View getItemView( int section, int row, View convertView, ViewGroup parent, LayoutInflater inflater ) {
Object item = getItemInSection(section, row);
if (convertView == null)
convertView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
TextView text = (TextView)convertView;
text.setText(item.toString());
return convertView;
}
}
Общий
Если поместить некий набор данных в приведенный выше адаптер, то можно получить что-то похожее на (если поиграться с getViewTypeForSection, то легко получить вторую картинку): |
|
Теги: #Android #списки #список разделов #Разработка Android
-
Вы Ищете Дешевый Планшетный Компьютер?
19 Oct, 24 -
8 Миллионов Показов Баннеров Без Результата
19 Oct, 24 -
Высокое Искусство 21 Века.
19 Oct, 24 -
Z-Music.ru - Обновленный Поиск Музыки
19 Oct, 24