Список Разделов В Android

Списки, разбитые на разделы, довольно часто встречаются как в системных приложениях, так и в сторонних.

Но, как ни странно, платформа не предлагает способов их реализации «из коробки», в отличие от iOS.

Список разделов в Android

Я знаю два подхода к реализации заголовков списков:

  • В макете каждого элемента списка прописывается заголовок, видимость которого будет определяться условием «является ли этот элемент первым в разделеЭ»
  • Заголовок списка создается отдельно и в методе Адаптер#getView есть выбор: создать элемент типа «заголовок», либо элемент типа «обычная линия»
Первый способ проще с точки зрения написания кода: не нужно думать о выбор типа ячейки списка .

Но это влечет за собой более «тяжелый» интерфейс с точки зрения производительности.

Второй немного сложнее в реализации, но немного быстрее и, что самое главное, позволяет использовать макеты системы .

В этой статье используется второй подход.

Базовый класс адаптера
Базовый адаптер берет на себя ответственность за определение типа строки списка и взамен запрашивает у своих преемников возможность создавать заголовки и строки данных:
  
  
  
  
  
  
  
   

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

Далее при изменении данных в списке (при вызове уведомитьDataSetChanged ), мы подсчитываем общее количество строк в списке, кэшируем количество элементов в каждом разделе, количество разных типов строк списка и ассоциацию этих типов с разделами:

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

Любые комментарии, предложения, а также ругательства по поводу стиля кодирования приветствуются.

Теги: #Android #списки #список разделов #Разработка Android

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.