-
Notifications
You must be signed in to change notification settings - Fork 351
2.06. ListView と ViewPager
GitHub Pagesへ移行しましたmixi-inc.github.ioへお願いします。
wikiの方はしばらくしたら消していきます
この章では、ListView と ViewPager の2つの特殊な View について解説します。
参考:Building Layouts with an Adapter | Android Developers
参考:Making ListView Scrolling Smooth | Android Developers
参考:PagerAdapter | Android Developers
参考:FragmentPagerAdapter | Android Developers
- ListView - ListAdapter - ListViewを表示する - カスタマイズしたリストアイテムを表示する - Viewの再利用について - パフォーマンスについて
- ViewPager - PagerAdapter - FragmentPagerAdapter
ListView は、縦にスクロールする一覧表示のための View です。
ListView 自身には、一覧の中身を管理する機能はありません。
代わりに、Adapter という仕組みを用いて、データソースの管理と、データの View へのバインドをさせ、ListView は、スクロール位置に合わせて必要な View を Adapter から取り出すことをします。
リストデータは、List インタフェースを実装したデータソースや、或いは、データベースへ問い合わせた結果のデータソースである場合もあります。
データをバインドして表示する際には、 ListAdapter インタフェースを実装した Adapter を使用します。
Adapter にはいくつかの種類が提供されています。以下はその一部となります。
| 名前 | 役割 |
|---|---|
| BaseAdapter | ListView と Spinner で使用可能な共通基底クラスです。 ListView では ListAdapter 用の interface を実装します |
| ArrayAdapter | 配列やリストデータソースをバインドする際に使用します |
| CursorAdapter | データベースへ問い合わせた結果をバインドする際に使用します |
| SimpleCursorAdapter | データベースへ問い合わせた結果を View へ容易にバインドできるようにしたCursorAdapterです |
ListView を表示するには以下の作業が必要となります。
- ListView をレイアウトxmlに配置
- ListView に Adapter をセットする
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <!-- ListViewを配置 --> <ListView android:id="@+id/ListView" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
MainActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mActivity = this; // ListViewに表示するデータを作成する ArrayList<String> list = new ArrayList<String>(); for (int i = 0; i < 20; i++) { list.add("hoge" + i); } ListView listView = (ListView) findViewById(R.id.ListView); // android.R.layout.simple_list_item_1はAndroidで既に定義されているリストアイテムのレイアウトです ArrayAdapter<String> adapter = new ArrayAdapter<String>(mActivity, android.R.layout.simple_list_item_1, list); listView.setAdapter(adapter); // タップした時の動作を定義する listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // Adapterからタップした位置のデータを取得する String str = (String) parent.getItemAtPosition(position); Toast.makeText(mActivity, str, Toast.LENGTH_SHORT).show(); } }); }
まずは ArrayAdapter のインスタンスを作成します。
ここではコンストラクタの引数に Context とリストアイテムのレイアウトIDと表示するデータをリスト形式で渡しています。
ArrayAdapter には様々なコンストラクタが用意されています。適宜、適切なものを使用すると良いでしょう。
レイアウトIDにはandroid.R.layout.simple_list_item_1を設定しています。これは Android で用意されているリストアイテムのレイアウトです。中には TextView が1つあるだけです。
表現豊かたなレイアウトを使用したい場合、自分で作成したレイアウトをリストアイテムに表示することもできます。
その方法は次項リストアイテムをカスタマイズするにて説明します。
次に、 setAdapter メソッドで ListView に Adapter をセットします。
最後に、 setOnItemClickListener メソッドでリストをタップした時の動作を設定しています。
実行すると以下の様なListViewが表示されます。
listview
カスタマイズしたリストアイテムを表示するには以下の作業が必要となります。
- リストアイテムのレイアウトxmlの作成
- 独自 Adapter の作成
新規にレイアウトxmlを作成します。
リストアイテムのレイアウトは以下のように作成します。
custom_list_item_layout
custom_list_item.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="4dp" > <ImageView android:id="@+id/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:src="@drawable/ic_launcher" /> <TextView android:id="@+id/TitleText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:layout_toRightOf="@+id/imageView1" android:textSize="18sp" /> <TextView android:id="@+id/SubTitleText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/TitleText" android:layout_alignParentRight="true" android:layout_below="@+id/TitleText" /> </RelativeLayout>
Adapter 内ではリストアイテム View の生成と表示位置に対応するデータの取得、設定といった処理を行います。
CustomListItemAdapter.java
public class CustomListItemAdapter extends ArrayAdapter<String> { private LayoutInflater mLayoutInflater; public CustomListItemAdapter(Context context, List<String> objects) { // 第2引数はtextViewResourceIdとされていますが、カスタムリストアイテムを使用する場合は特に意識する必要のない引数です super(context, 0, objects); // レイアウト生成に使用するインフレーター mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = null; // ListViewに表示する分のレイアウトが生成されていない場合レイアウトを作成する if (convertView == null) { // レイアウトファイルからViewを生成する view = mLayoutInflater.inflate(R.layout.custom_list_item, parent, false); } else { // レイアウトが存在する場合は再利用する view = convertView; } // リストアイテムに対応するデータを取得する String item = getItem(position); // 各Viewに表示する情報を設定 TextView text1 = (TextView) view.findViewById(R.id.TitleText); text1.setText("Title:" + item); TextView text2 = (TextView) view.findViewById(R.id.SubTitleText); text2.setText("SubTitle:" + item); return view; } }
ArrayAdapter を継承して独自の Adapter を作成しています。
getView メソッドをオーバーライドして、戻り値に表示するリストアイテムの View を返します。
表示している位置のデータは getItem メソッドで取得することができます。
custom_list_item
Android ではリストアイテムを表示する際に、既に View が生成されていてる場合はそれを再利用します。
リストに表示するデータが50件あった場合、リストアイテムの View を50個を生成することはしないということです。
画面を構成するのに必要な分の View が生成され、あとは再利用するため無駄な View を生成するといったことはありません。
どのように View が再利用されているかを確認するため先ほどの CustomListItemAdapter クラスの getView メソッドを以下のように修正します。
CustomListItemAdapter.java
@Override public View getView(int position, View convertView, ViewGroup parent) { View view = null; // ListViewに表示する分のレイアウトが生成されていない場合レイアウトを作成する if (convertView == null) { // レイアウトファイルからViewを生成する view = mLayoutInflater.inflate(R.layout.custom_list_item, parent, false); // リストアイテムに対応するデータを取得する String item = getItem(position); // 各Viewに表示する情報を設定 TextView text1 = (TextView) view.findViewById(R.id.TitleText); text1.setText("Title:" + item); TextView text2 = (TextView) view.findViewById(R.id.SubTitleText); text2.setText("SubTitle:" + item); } else { // レイアウトが存在する場合は再利用する view = convertView; } // 一度作成したレイアウトの表示データを変更しないことにより、再利用されたデータがどこに表示されるかを確認する return view; }
実行すると以下のように View が使いまわされていることがわかります。
recycle_list_item
ListView がスクロールされるたびに findViewById メソッドを呼び出すとパフォーマンスが低下します。
再利用した View を表示するときでも各要素を更新することがあります。
頻繁に findViewById メソッドを呼び出す場合には"view holder"デザインパターンを利用すると良いです。
詳しくは、 Hold View Objects in a View Holder| Android Developersを御覧ください。
ViewPager は、横にフリックして View を切り替えるための View です。
単純な View だけでなく、Fragment を持たせて、複数の Fragment を切り替える用途にも使用出来ます。
ViewPager も、ListView と同じく、Adapter に中身を管理させます。
また、ViewPager は Support Package で提供されている機能です。
データをバインドして表示する際には、ListAdapter インタフェースを実装した Adapter を使用します。
PagerAdapter は ViewPager を表示するための基本的な Adapter です。
ここでは TextView をもつページが5つある ViewPager を作成します。
ViewPager を表示するには以下の作業が必要となります。
- ViewPager をレイアウトxmlに配置
- 独自 PagerAdapter を実装
- ViewPager に Adapter をセットする
ViewPager は Support Package で提供されているため、android.support.v4.view.ViewPagerと記述します。
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <android.support.v4.view.ViewPager android:id="@+id/Pager" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
PagerAdapter を実装するには最低限以下のメソッドを実装する必要があります。
| メソッド名 | 役割 |
|---|---|
| Object instantiateItem(ViewGroup container, int position) | ページを生成します |
| void destroyItem(ViewGroup container, int position, Object object) | ページを削除します |
| int getCount() | ページ数を返却します |
| boolean isViewFromObject(View view, Object object) | instantiateItemで返却されたキーObjectとページが関連付いているかどうかを確認します。 |
SamplePagerAdapter.java
class SamplePagerAdapter extends PagerAdapter { private static final int PAGE_COUNT = 5; private Context mContext; public SamplePagerAdapter(Context context) { super(); mContext = context; } @Override public Object instantiateItem(ViewGroup container, int position) { // TextViewを生成 TextView textView = new TextView(mContext); textView.setText("Position:" + position); //コンテナに追加 container.addView(textView); // ここではTextView自体をキーとして返しています return textView; } @Override public void destroyItem(ViewGroup container, int position, Object object) { // viewの削除 // objectはinstantiateItemで返却したオブジェクトです container.removeView((View) object); } @Override public int getCount() { // ページ数を返します。今回は固定値としています。 return PAGE_COUNT; } @Override public boolean isViewFromObject(View view, Object object) { // キーが正しいかを判定 return view == (TextView) object; }
ViewPager はキーとなる object でそれぞれのページを関連付けています。このキーは instantiateItem メソッドで返却された object であり Adapter 内でのユニークな識別子として使用されます。
上記のコードでは TextView 自身をキーとして使用しています。
MainActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewPager pager = (ViewPager) findViewById(R.id.Pager); pager.setAdapter(new SamplePagerAdapter(this)); }
ViewPager は Fragment の切り替えに使われるケースが多いです。
Fragment は簡単に提供できますし、それぞれのページのライフサイクルの管理ができるためです。
ViewPager で Fragment を使う際のよくあるユースケースをカバーし実装されたものが、 FragmentPagerAdapter と FragmentStatePagerAdapter になります。
FragmentPagerAdapter は表示したページをメモリに保持します。 FragmentStatePagerAdapter は非表示になったページはステートの保持のみを行い Fragment を破棄します。ページ数が多い場合メモリ消費が大きくなるため使用すると良いです。
- ViewPager をレイアウトxmlに配置
- Fragment を作成
- 独自 FragmentPagerAdapter を実装
- ViewPager にAdapter をセットする
PagerAdapter の使い方と同様のため割愛します。
サンプルでは表示位置(position)を TextView 表示します。
そのため、newInstance メソッドを用意し position を引数として渡し Bundle を設定しています。
Bundle から position を取得し、TextView に設定しています。
fragment_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" > <TextView android:id="@+id/TextView1" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="4dp" android:background="@android:color/white" android:gravity="center" android:textSize="90sp" android:textStyle="bold" /> </FrameLayout>
SampleFragment.java
public class SampleFragment extends Fragment { public static SampleFragment newInstance(int position) { SampleFragment sampleFragment = new SampleFragment(); Bundle bundle = new Bundle(); bundle.putInt("position", position); sampleFragment.setArguments(bundle); return sampleFragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Bundle bundle = getArguments(); int position = 0; if (bundle != null) { position = bundle.getInt("position"); } View view = inflater.inflate(R.layout.fragment_main, container, false); TextView text = (TextView) view.findViewById(R.id.TextView1); text.setText(String.valueOf(position)); return view; } }
FragmentPagerAdapter を実装するには最低限以下のメソッドを実装する必要があります。
| メソッド名 | 役割 |
|---|---|
| Fragment getItem (int position) | Fragmentを生成します |
| int getCount() | ページ数を返します |
| SampleFragmentPagerAdapter.java |
public class SampleFragmentPagerAdapter extends FragmentPagerAdapter { private static final int PAGE_COUNT = 5; public SampleFragmentPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return SampleFragment.newInstance(position); } @Override public int getCount() { return PAGE_COUNT; } }
Fragment を管理するため FragmentManager をコンストラクタに渡します。
Support Package を使用しているため、 FragmentManager を生成するメソッド名は getSupportFragmentManager となります。
( Support Package でない場合は getFragmentManager となります)
MainActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewPager pager = (ViewPager) findViewById(R.id.Pager); FragmentManager fm = getSupportFragmentManager(); SampleFragmentPagerAdapter sampleFragmentPagerAdapter = new SampleFragmentPagerAdapter(fm); pager.setAdapter(sampleFragmentPagerAdapter); }
- (実習) カスタマイズしたリストアイテムを表示するで説明したプロジェクトを写経して実行してください。余裕がある場合はViewの再利用についてについても試してみて下さい。説明したプロジェクトは
AndroidTraining/projects/fundamentals/6th/CustomListItemSampleにあります。 - (実習) 実習1の画面にボタンを配置し、ボタンをタップしたらListViewの先頭を表示するようにして下さい。
ヒント:ListView には smoothScrollToPosition メソッドがあります。
- (課題) 以下のListViewを表示するように実装してください。
-
スクリーンショット
ListAdapterには予め用意されているBookクラスを使用するようにして下さい。
- (課題) 課題3で作成したListViewをタップしたら、次画面に遷移してタップしたアイテムの情報を表示するようにしてください。
- (課題) 課題3のContextMenuにListViewが設定されています。ContextMenuには追加と削除メニューが定義されています。追加をタップしたときは選択されているアイテムを新たなアイテムとしてListViewにデータを追加してください。削除をタップしたときは選択されているアイテムを削除するように実装してください。
ヒント:データの追加、削除、表示の更新処理はAdapterで行います。
- (実習) [FragmentPagerAdapter の使い方](#FragmentPagerAdapter の使い方)で説明したプロジェクトを写経して実行してください。説明したプロジェクトは
AndroidTraining/projects/fundamentals/6th/FragmentViewPagerSampleにあります。 - (課題) 実習1のプロジェクトを引き続き使用してください。ActionBarのTab Navigationを追加し、Tabを選択したら該当のページが表示されるようにしてください。(3番目のTabを選択したら3ページ目が表示される)また、ページをスクロールさせたら該当のTabが選択されるようにしてください。 また、Tabとページの数は3つとしてください。