2011年11月27日日曜日

[Android][ListView][ViewPager]ViewPagerの中でListViewを使用する

スワイプによるページ切り替えを実装するのに、ViewPagerを使うことができます。今、自分が作っているアプリで、ViewPagerの中にListViewを配置する必要があったのですが、実装で結構ハマってしまったのでご紹介します。

ViewPagerの基本的な使い方は書きリンクを参照してください。

スワイプ動作でページ送りする(ViewPager) « Tech Booster


ListViewをカスタマイズする | Android Techfirm Lab




ハマったところ


自分がハマったのが、ListViewのContextMenuからリストのアイテムに対して変更をかける処理です。ViewPagerを使っていない時のようにListAdapterにアイテムを追加・削除してもうまく反映されませんでした。追加しても次のページのListViewにアイテムが追加されてしまったり、アイテムを取得しようとしても、IndexOutOfBoundsExceptionが発生したりしました。

原因


この原因は、アイテムの追加や削除をしようとしているListAdapterが、現在ページに表示しているListViewと一致していないためでした。

自分が実装した方法では、PageAdapterを実装したクラスのinstantiateItem()内でListViewとListAdapterを生成しているため、 ページ数分だけListViewとListAdapterが作られます。ViewPagerは、現在表示しているページと、前後のページの3ページ分のページを生成します。なので、今操作したいListAdapterに対して、アイテムの追加や削除をする必要があります。

解決法


onCreateContextMenu()で引数のListViewからListAdapterを取り出し保持しておきます。そうすることで、onContextItemSelected()等のコールバックリスナーでContextMenuを生成したListViewのListAdapterを使うことができます。

コード


ViewPager内でListViewを実装したサンプルコードです。ContextMenuをタップした時の処理や、ListViewのアイテムをタップした時の処理を記載してあります。

まずは、レイアウトのXMLから。

main.xml


アクティビティのレイアウトです。

[xml]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1" />

</LinearLayout>

[/xml]

ViewPagerは置いてあるだけの単純なレイアウトです。

list.xml


ViewPagerで読み込むページのレイアウトになります。

[xml]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:weightSum="1">

<ListView android:id="@+id/listView1"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:cacheColorHint="#00000000"></ListView>
</LinearLayout>

[/xml]

ページにはListViewが配置してあるだけです。

list_row.xml


ListViewのアイテムのレイアウトです。

[xml]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10dip">
<TextView android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textStyle="bold">
</TextView>
</LinearLayout>

[/xml]

アイテムにはTextViewが1つだけ配置してあります。

MyListAdapter.java


ListViewのListAdapterです。

[java]
package jp.inara.sample.ViewPagerListView;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class MyListAdapter extends ArrayAdapter {

private ArrayList mItems;
private LayoutInflater mInflater;

public MyListAdapter(Context context, int textViewResourceId, List list) {
super(context, textViewResourceId, list);
this.mItems = (ArrayList) list;
this.mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view == null) {
view = mInflater.inflate(R.layout.list_row, null);
}
String item = mItems.get(position);
if (item != null) {
TextView textView = (TextView)view.findViewById(R.id.text);
textView.setText(item);
}
return view;
}

}
[/java]

やっている事は、普通にListViewを表示する時と同じです。getView()内でアイテム用のViewGroupを生成し、TextViewに値を設定しているだけです。

MainActivity.java


メインのアクティビティです。

[java highlight="”144,156″"]
package jp.inara.sample.ViewPagerListView;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.LinearLayout;
import android.widget.ListView;

public class MainActivity extends Activity {
private Context mContext;
private MyPagerAdapter mPagerAdapter;
private ViewPager mViewPager;
private List mList;
private MyListAdapter mCurrentListAdapter;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

mContext = this;
mPagerAdapter = new MyPagerAdapter();
mViewPager = (ViewPager) findViewById(R.id.viewpager);
mViewPager.setAdapter(mPagerAdapter);
}

private class MyPagerAdapter extends PagerAdapter {

@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}

@Override
public void destroyItem(View container, int position, Object object) {
((ViewPager) container).removeView((LinearLayout) object);
}

@Override
public void finishUpdate(View arg0) {
// TODO 自動生成されたメソッド・スタブ

}

@Override
public int getCount() {
// TODO 自動生成されたメソッド・スタブ
return 3;
}

@Override
public Object instantiateItem(View container, int position) {

LayoutInflater mInflater = (LayoutInflater) getApplicationContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout mLinearLayout = (LinearLayout) mInflater.inflate(
R.layout.list, null, false);

mList = new ArrayList();
final MyListAdapter adapter = new MyListAdapter(mContext,
R.layout.list_row, mList);
ListView listView = (ListView) mLinearLayout
.findViewById(R.id.listView1);

listView.setAdapter(adapter);
mList.add("aaa");
mList.add("bbb");
mList.add("ccc");

// リストビュークリック時のコールバックリスナー
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view,
int position, long id) {

adapter.add("ListClick Add!");

// 外部のメソッドで更新するためにMyListAdaptorを保持する
mCurrentListAdapter = adapter;

// ListViewを更新
updateList();

}
});

// ContextMenuに登録
registerForContextMenu(listView);

// ViewPagerにViewGroupを追加
((ViewPager) container).addView(mLinearLayout, 0);

return mLinearLayout;
}

@Override
public boolean isViewFromObject(View view, Object object) {
// TODO 自動生成されたメソッド・スタブ
return view == ((LinearLayout) object);
}

@Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
// TODO 自動生成されたメソッド・スタブ

}

@Override
public Parcelable saveState() {
// TODO 自動生成されたメソッド・スタブ
return null;
}

@Override
public void startUpdate(View arg0) {
// TODO 自動生成されたメソッド・スタブ

}

}

// ContextMenu生成処理
@Override
public void onCreateContextMenu(ContextMenu menu, View view,
ContextMenuInfo info) {
super.onCreateContextMenu(menu, view, info);
AdapterContextMenuInfo adapterInfo = (AdapterContextMenuInfo) info;
ListView lView = (ListView) view;
mCurrentListAdapter = (MyListAdapter) lView.getAdapter();

String item = (String) lView.getItemAtPosition(adapterInfo.position);
menu.setHeaderTitle((String) item);
menu.add(Menu.NONE, Menu.FIRST, Menu.NONE, "削除");
}

// ContextMenuタップ時のコールバックリスナー
@Override
public boolean onContextItemSelected(MenuItem item) {

AdapterContextMenuInfo adapterInfo = (AdapterContextMenuInfo) item.getMenuInfo();
String text = mCurrentListAdapter.getItem(adapterInfo.position);

switch (item.getItemId()) {
case Menu.FIRST:

mCurrentListAdapter.remove(text);
// Listを更新
updateList();

return true;

default:
return super.onContextItemSelected(item);
}
}

/**
* Listを更新する
*/
private void updateList() {
mCurrentListAdapter.notifyDataSetChanged();
}
}
[/java]

ContextMenu生成時にListAdapterを保持するために、メンバにmCurrentListAdapterを定義しています。onCreateContextMenuの第2引数にContextMenuを生成したListViewが返されてきます。そこからListAdapterを取り出します。

後はonCotextItemSelectedでCurrentListAdapterを使って選択したアイテムを削除しています。

ひとりごと


気づいてしまえばどうってことないんですけど、ListAdapterがページ数分存在する事に気づかず1日くらいハマっていました。こういう事に気づかないってことはちゃんと仕組みを理解してないからなんだろうなぁーと思います。サンプルのマネするだけじゃなく、メソッドの意味とか理解しなとダメですね。。。

0 件のコメント:

コメントを投稿