Androidセクションリスト

セクションに分割されたリストは、システムアプリケーションとサードパーティの両方で非常に一般的です。 しかし、奇妙なことに、このプラットフォームは、iOSとは異なり、すぐに実装できる方法を提供していません。

画像



リストヘッダーを実装する2つのアプローチを知っています。



最初の方法は、コードを記述するという点で簡単です。 リストセルのタイプを選択することについて考える必要はありません。 しかし、パフォーマンス、インターフェースの点で、より「難しい」ことを伴います。 2番目は実装が少し面倒ですが、もう少し高速であり、最も重要なこととして、 システムレイアウトを使用できるようにします

この記事では、2番目のアプローチを使用します。



アダプターの基本クラス


基本アダプタは、リスト行のタイプを決定する責任を負い、代わりに、相続人にヘッダーとデータ行を作成できるように要求します。



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







さらに、リスト内のデータを変更するとき( notifyDataSetChangedが呼び出されるとき)、リスト内の行の総数を考慮し、各セクションの要素数、さまざまなタイプのリスト行の数、およびこれらのタイプとセクションの関係をキャッシュします。

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





原則として、セクション内のセクション番号とインデックスを取得する方法は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 ); }
      
      







使用例


簡単な例:入り口に「セクション」のリストを入力し、セクションからすべてのデータを1つのスタイルで描画します。

セクションには、ヘッダーのタイトルとデータセットが表示されます。

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







タイトルは、1つの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で遊ぶ場合、2番目の図を簡単に取得できます)。

画像画像


コーディングスタイルに関するコメント、提案、呪いは歓迎します。



All Articles