📜 ⬆️ ⬇️

Android sectional list

Lists, divided into sections, are found quite often both in system applications and in third-party applications. But, oddly enough, the platform does not offer ways to implement them out of the box, unlike iOS.
image

I know two approaches to the implementation of list headers:

The first method is simpler from the point of view of writing code: no need to think about choosing the type of the list cell . But entails a more "heavy", from the point of view of performance, interface. The second is a bit more cumbersome in implementation, but a little faster and, most importantly, allows the use of system layouts .
This article uses the second approach.

Adapter Base Class

The base adapter takes responsibility for determining the type of a list row, instead asks its heirs to be able to create headers and data lines:

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

')
Further, when changing the data in the list (when calling notifyDataSetChanged ), we count the total number of rows in the list, cache the number of elements in each section, the number of different types of lines in the list, and the connection of these types with sections:
  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(); } 


Based on the information collected, you can easily choose what we need to create in 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); } 


Determine whether the string is a section header or element, like this:
  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; } 

In principle, the methods for obtaining the section number and index inside the section can be combined into one, the separation is left for clarity.
Base class entirely:
 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 ); } 


Usage example

A simple example: at the input we submit a list of “sections”, draw all the data of sections with one style.
The section represents the title and data set:
 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; } } 


The title uses the simplest layout consisting of one 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" /> 


Simple adapter entirely:

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


Total

If you set some data on the adapter above, you can get something similar to (if you play around with getViewTypeForSection, you can easily get the second picture):
imageimage

Any comments, suggestions, as well as expletives regarding the coding style are welcome.

Source: https://habr.com/ru/post/150615/


All Articles