2011年3月27日日曜日

ListViewにグループタイトルをつける


上記のようにアルファベットなどでグルーピングされたリストに
グループタイトルをつける方法を考えてみた。
(PreferenceCategoryのタイトルのようなものです)

まず、ListViewの1行分のレイアウトを用意する。
その際、グループタイトル+通常要素(アルバム)となるようにする。
上記のようなアルバム一覧の場合、下記のようなレイアウトを用意する。


具体的なレイアウトのXMLは下記のようになる。
list_item.xml
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:orientation="vertical"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="fill_parent"  
  6.     >  
  7.     <!-- グループタイトル -->  
  8.     <TextView  
  9.         android:id="@+id/groupTitle"  
  10.         android:layout_width="fill_parent"  
  11.         android:layout_height="wrap_content"  
  12.         android:background="#280030"  
  13.         android:textColor="#c62b00"  
  14.         android:textStyle="bold"  
  15.         android:paddingLeft="5dp"  
  16.         />  
  17.     <!-- アルバム名 -->  
  18.     <TextView  
  19.         android:id="@+id/albumName"  
  20.         android:layout_width="fill_parent"  
  21.         android:layout_height="wrap_content"  
  22.         android:textColor="#FFFFFF"  
  23.         android:textSize="24sp"  
  24.         android:paddingLeft="5dp"  
  25.         />  
  26.     <!-- アーティスト名 -->  
  27.     <TextView  
  28.         android:id="@+id/artistName"  
  29.         android:layout_width="fill_parent"  
  30.         android:layout_height="wrap_content"  
  31.         android:paddingLeft="5dp"  
  32.         />  
  33. </LinearLayout>  

続いてArrayAdapterを継承したクラスを実装する。
MyAdapter.java
  1. package jp.u1aryz.products.grouptitle;  
  2.   
  3. import java.util.List;  
  4.   
  5. import android.content.Context;  
  6. import android.view.LayoutInflater;  
  7. import android.view.View;  
  8. import android.view.ViewGroup;  
  9. import android.widget.ArrayAdapter;  
  10. import android.widget.TextView;  
  11.   
  12. class BindData {  
  13.     String groupTitle;  
  14.     String albumName;  
  15.     String artistName;  
  16.   
  17.     public BindData(String groupTitle, String albumName, String artistName) {  
  18.         this.groupTitle = groupTitle;  
  19.         this.albumName = albumName;  
  20.         this.artistName = artistName;  
  21.     }  
  22. }  
  23.   
  24. class ViewHolder {  
  25.     TextView groupTitle;  
  26.     TextView albumName;  
  27.     TextView artistName;  
  28. }  
  29.   
  30. public class MyAdapter extends ArrayAdapter<BindData> {  
  31.   
  32.     private LayoutInflater inflater;  
  33.   
  34.     public MyAdapter(Context context, List<BindData> objects) {  
  35.         super(context, 0, objects);  
  36.         this.inflater = (LayoutInflater) context  
  37.             .getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
  38.     }  
  39.   
  40.     @Override  
  41.     public boolean isEnabled(int position) {  
  42.         // BindDataのgroupTitleが!nullの場合、選択不可にする  
  43.         return getItem(position).groupTitle == null;  
  44.     }  
  45.   
  46.     @Override  
  47.     public View getView(int position, View convertView, ViewGroup parent) {  
  48.         ViewHolder holder;  
  49.   
  50.         if (convertView == null) {  
  51.             convertView = inflater.inflate(R.layout.list_item, parent, false);  
  52.             holder = new ViewHolder();  
  53.             holder.groupTitle = (TextView) convertView.findViewById(R.id.groupTitle);  
  54.             holder.albumName = (TextView) convertView.findViewById(R.id.albumName);  
  55.             holder.artistName = (TextView) convertView.findViewById(R.id.artistName);  
  56.             convertView.setTag(holder);  
  57.         } else {  
  58.             holder = (ViewHolder) convertView.getTag();  
  59.         }  
  60.   
  61.         BindData data = getItem(position);  
  62.         // グループタイトル  
  63.         if (!isEnabled(position)) {  
  64.             holder.groupTitle.setVisibility(View.VISIBLE);  
  65.             holder.groupTitle.setText(data.groupTitle);  
  66.             holder.albumName.setVisibility(View.GONE);  
  67.             holder.artistName.setVisibility(View.GONE);  
  68.         // アルバム  
  69.         } else {  
  70.             holder.groupTitle.setVisibility(View.GONE);  
  71.             holder.albumName.setVisibility(View.VISIBLE);  
  72.             holder.albumName.setText(data.albumName);  
  73.             holder.artistName.setVisibility(View.VISIBLE);  
  74.             holder.artistName.setText(data.artistName);  
  75.         }  
  76.         return convertView;  
  77.     }  
  78. }  

ここでは選択不可の項目を設定する際、BindDataのgroupTileが!nullだった場合と
ルール決めをしている。
ポイントは63行目〜75行目になり、View#setVisibility(int visibility)を使用し、
各項目を表示、非表示と切り替える。

最後にActivityから以下のようにしてadapterを設定する。
  1. package jp.u1aryz.products.grouptitle;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import android.app.ListActivity;  
  7. import android.os.Bundle;  
  8.   
  9. public class MyActivity extends ListActivity {  
  10.   
  11.     @Override  
  12.     public void onCreate(Bundle savedInstanceState) {  
  13.         super.onCreate(savedInstanceState);  
  14.         setContentView(R.layout.main);  
  15.   
  16.         setTitle("アルバム一覧");  
  17.   
  18.         List<BindData> list = new ArrayList<BindData>();  
  19.         // ここにlistに項目を追加する処理が入る  
  20.   
  21.         MyAdapter adapter = new MyAdapter(this, list);  
  22.         setListAdapter(adapter);  
  23.     }  
  24. }  

正直もっといい方法がありそうなので、より良い方法が見つかったら更新するかも。。。

2011年3月6日日曜日

ListViewのスクロール位置のあれこれ


AndroidMarketアプリやYouTubeアプリ、Gmailアプリなどでは
ListViewを最後までスクロールすると自動的に次の数件を取得し表示される。

android.widget.AbsListView.OnScrollListener#onScrollを利用すると
表示されている先頭のインデックス(firstVisibleItem)、
表示されているリストの数(visibleItemCount)、リストのトータル数(totalItemCount)
が引数として渡ってくるので、firstVisibleItem + visibleItemCount = totalItemCount
になる時、最後までスクロールされたと判定出来る。


上図で赤枠部分が画面に表示されている部分になる。
最後までスクロールしている右図でfirstVisibleItem + visibleItemCount = totalItemCount
になっていることがわかる。

下記がソースの一部。
  1. ListView listView = (ListView) findViewById(android.R.id.list);  
  2. listView.setOnScrollListener(new OnScrollListener() {  
  3.   
  4.     @Override  
  5.     public void onScrollStateChanged(AbsListView view, int scrollState) {  
  6.     }  
  7.   
  8.     @Override  
  9.     public void onScroll(AbsListView view, int firstVisibleItem,  
  10.             int visibleItemCount, int totalItemCount) {  
  11.         // 最後までスクロールされたかどうかの判定  
  12.         if (totalItemCount == firstVisibleItem + visibleItemCount) {  
  13.             // ここに次の数件を取得して表示する処理を書けばいい  
  14.         }  
  15.     }  
  16. });  

リストの追加などでListViewを更新する際、スクロール位置が毎回戻ってしまっては
ユーザービリティの悪いものになってしまう。
ListViewのスクロール位置の取得と設定は下記のようにして実現出来る。

取得
  1. int position = listView.getFirstVisiblePosition();  
  2. int y = listView.getChildAt(0).getTop();  

設定
  1. listView.setSelectionFromTop(position, y);  

インターネット上からデータを取得する際に使用する頻度が多いかも。