カスタムレイアウトで Single Choice の ListView を作る方法はいくつかあります。ここでは以下の二つの方法を考えます。
- カスタムレイアウトに Checkable インタフェースを実装する
- ArrayAdapter に Single Choice 機能を実装する
まず以下の様な単純な ListView を考えます。
public class MainActivity extends Activity {
private static final String[] FRUITS = {
"Apple", "Banana", "Lemon", "Orange", "Grape"
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listView = new ListView(this);
listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
ArrayAdapter<String> adapter =
new ArrayAdapter<>(this, android.R.layout.simple_list_item_single_choice);
adapter.addAll(FRUITS);
listView.setAdapter(adapter);
setContentView(listView);
}
}
ここではまだカスタムレイアウトは使っていません。simple_list_item_single_choice を使った単純なものです。これをカスタムレイアウトを使うよう修正してみましょう。上で述べた二つの方法を別々に実装してみたいと思います。
カスタムレイアウトに Checkable インタフェースを実装する
まずレイアウトに Checkable インタフェースを実装します。レイアウトは何でも構いませんが、ここでは LinearLayout を使います。
public class CheckableLinearLayout extends LinearLayout implements Checkable {
private RadioButton mRadioButton;
public CheckableLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mRadioButton = (RadioButton) findViewById(R.id.radio_button);
}
@Override
public void setChecked(boolean b) {
mRadioButton.setChecked(b);
}
@Override
public boolean isChecked() {
return mRadioButton.isChecked();
}
@Override
public void toggle() {
mRadioButton.toggle();
}
}
Checkable インタフェースの処理は全て RadioButton に委譲します。
次にこのレイアウトを使ったレイアウトXML (item_layout_checkable.xml) を作成します。
次にこのレイアウトを使ったレイアウトXML (item_layout_checkable.xml) を作成します。
<com.example.singlechoicetest.CheckableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/text_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
<!-- フォーカスを持ったり、クリックイベントを拾わないようにする -->
<RadioButton
android:id="@+id/radio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:focusable="false"
android:focusableInTouchMode="false"
android:clickable="false"
/>
</com.example.singlechoicetest.CheckableLinearLayout>
ここで気をつけなくてはいけないのは、RadioButton がフォーカスを持ったり、クリックイベントを拾わないようにすることです。これをしないとクリックイベントが ListView に伝わらず、SingleChoice 動作ができません。
更にこのレイアウトを使う ArrayAdapter を作成します。
更にこのレイアウトを使う ArrayAdapter を作成します。
public final class FruitAdapter extends ArrayAdapter<String> {
private static final String[] FRUITS = {
"Apple", "Banana", "Lemon", "Orange", "Grape"
};
private final LayoutInflater mInflater;
FruitAdapter(Context context) {
super(context, 0);
addAll(FRUITS);
mInflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view = mInflater.inflate(R.layout.item_layout_checkable, parent, false);
}
TextView textView = (TextView) view.findViewById(R.id.text_view);
String fruit = getItem(position);
textView.setText(fruit);
return view;
}
}
これだけ単純なレイアウトであればわざわざ ArrayAdapter のサブクラスを作る必要もありませんが、一応複雑なレイアウトも想定して敢えてサブクラス化しました。また、getView() では ViewHolder パターンを使うべきですが、ここでは省略します。
最後にこれらを使って ListView を作ってみましょう。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listView = new ListView(this);
listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
ListAdapter adapter = new FruitAdapter(this);
listView.setAdapter(adapter);
setContentView(listView);
}
}
はい、これで出来上がり。この方法のちょっと嫌な所は、CheckableLinearLayout と XMLレイアウトが密に結び付いていることです。
ArrayAdapter に Single Choice 機能を実装する
次に別の方法、Checkable インタフェースを使わず、ArrayAdapter に Single Choice 機能を実装する方法を考えたいと思います。
まず、ArrayAdapter のサブクラスを作ります。
まず、ArrayAdapter のサブクラスを作ります。
public final class SingleChoiceAdapter extends ArrayAdapter<String> {
private static final String[] FRUITS = {
"Apple", "Banana", "Lemon", "Orange", "Grape"
};
private final LayoutInflater mInflater;
private int mSelectedIndex = -1;
SingleChoiceAdapter(Context context) {
super(context, 0);
addAll(FRUITS);
mInflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view = mInflater.inflate(R.layout.item_layout, parent, false);
}
// 本当はここで ViewHolder パターンを使うべきだが省略
TextView textView = (TextView) view.findViewById(R.id.text_view);
RadioButton radioButton = (RadioButton) view.findViewById(R.id.radio_button);
String fruit = getItem(position);
textView.setText(fruit);
radioButton.setChecked(position == mSelectedIndex);
return view;
}
void setSelectedIndex(int index) {
mSelectedIndex = index;
notifyDataSetChanged();
}
}
選択中のインデックスを保持するメンバー mSelectedIndex を用意し、getView() の中で適宜 RadioButton のチェック状態を設定します。また外部からこれを設定するメソッド setSelectedIndex(int) を作成します。このメソッドが呼ばれた時に View を更新するため、notifyDatasetChanged() を実行します。
この中で使っているレイアウトXML (item_layout.xml) は先程のものとほとんど同じですが、これも一応載せておきます。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/text_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
<!-- フォーカスを持ったり、クリックイベントを拾わないようにする -->
<RadioButton
android:id="@+id/radio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:focusable="false"
android:focusableInTouchMode="false"
android:clickable="false"
/>
</LinearLayout>
CheckableLinearLayout の代りに LinearLayout を使っているだけです。
後はこのカスタム ArrayAdapter を ListView に設定してやります。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listView = new ListView(this);
listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
final SingleChoiceAdapter adapter = new SingleChoiceAdapter(this);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
adapter.setSelectedIndex(i);
}
});
listView.setAdapter(adapter);
setContentView(listView);
}
}
ListView の onItemClick() で先程作成した setSelectedItem(int) を呼び出すようにします。
どちらの方法を使っても外見の動作は全く同じです。一応こんな感じになります。
テストに使ったソースコードをここに置きました。
https://github.com/masamichi441/AndroidSingleChoiceTest
0 件のコメント :
コメントを投稿