Android spinner にカスタムアダプターを作成

朝の散歩です。すがすがしい顔している柴犬です。
鳩は餌を探しているのか、怒っているのか分かりませんがアクションが激しい動きをしていました。
これでもかというくらい枯葉をかき回していました。距離にして足元から2m位のところでした。
2024年2月26日現在
WEBのみでは断片的で覚えにくいので最初に購入した Kotlin の本です。
概要
先日 stackoverflow の次の記事が感激したので、理解を進めるため丸写しに近いのですが一応動いたので記事にしてみました。
https://stackoverflow.com/questions/57433568/android-spinner-and-listview-with-the-same-arraylist
spinner のアダプターをカスタマイズ・クラスの使い方でこんなことができるのだ!!と驚くことばかりでした。
考え方が一歩前進しました。記事を書いた方に感謝です。
ファイルの構成は次のようにしています。

MainActivity.java
いきなりメインコードから始めます。
final String[] ken_name = {
"県・令制国一覧- ","愛知-尾張", "愛知-三河", "岐阜-美濃", "岐阜-飛騨", "三重-伊賀",
"三重-伊勢", "三重-志摩", "三重-紀伊", "福井-若狭", "福井-越前"};
がスピナーのドロー画面に表示される配列です。
クラス RowData のインスタンスが上の配列の要素毎に作成され、配列 rowList の一要素となります。
それをカスタムアダプター class CustomSpinnerAdapter に渡します。
package org.sibainu.relax.room.last2;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final String[] ken_name = {
"県・令制国一覧- ","愛知-尾張", "愛知-三河", "岐阜-美濃", "岐阜-飛騨", "三重-伊賀",
"三重-伊勢", "三重-志摩", "三重-紀伊", "福井-若狭", "福井-越前"};
Spinner spinner = (Spinner) findViewById(R.id.pre_spinner);
//Drawable drawable = ResourcesCompat.getDrawable(getResources(), R.drawable.spinner_border, null);
//spinner.setBackground(drawable);
ArrayList<RowData> rowList = new ArrayList<>();
for (int i = 0; i < ken_name.length; i++) {
RowData rowdata = new RowData();
rowdata.setKen(ken_name[i]);
rowdata.setSelected(false);
rowdata.setOnOffed(false);
rowList.add(rowdata);
}
CustomSpinnerAdapter spinnerAdapter = new CustomSpinnerAdapter(this, 0,
rowList);
spinner.setAdapter(spinnerAdapter);
Button btn;
btn = findViewById(R.id.button0);
// リスナーのインスタンスを作成
ClickListener cl = new ClickListener(spinnerAdapter);
btn.setOnClickListener(cl);
}
public class CustomSpinnerAdapter extends ArrayAdapter<RowData> {
private Context _context;
private ArrayList<RowData> _listState;
private CustomSpinnerAdapter _spinnerAdapter;
private boolean _isFromView = false;
public CustomSpinnerAdapter(Context context, int resource, List<RowData> objects) {
super(context, resource, objects);
this._context = context;
this._listState = (ArrayList<RowData>) objects;
this._spinnerAdapter = this;
}
@Override
public View getDropDownView(int posi, View convertView,
ViewGroup parent) {
return getCustomView(posi, convertView, parent);
}
@Override
public View getView(int posi, View convertView, ViewGroup parent) {
return getCustomView(posi, convertView, parent);
}
public View getCustomView(final int posi, View convertView,
ViewGroup parent) {
final RowDataHolder rowdata;
if (convertView == null) {
LayoutInflater layoutInflator = LayoutInflater.from(this._context);
convertView = layoutInflator.inflate(R.layout.item, null);
rowdata = new RowDataHolder();
rowdata.pref = (TextView) convertView.findViewById(R.id.prefTV);
rowdata.subpref = (TextView) convertView.findViewById(R.id.subprefTV);
rowdata.sw = (Switch) convertView.findViewById(R.id.switch1);
rowdata.cb = (CheckBox) convertView.findViewById(R.id.checkbox);
convertView.setTag(rowdata);
} else {
rowdata = (RowDataHolder) convertView.getTag();
}
rowdata.pref.setText(_listState.get(posi).getPref());
rowdata.subpref.setText(_listState.get(posi).getSubPref());
this._isFromView = true;
rowdata.cb.setChecked(this._listState.get(posi).isSelected());
rowdata.sw.setChecked(this._listState.get(posi).isOnOffed());
this._isFromView = false;
if ((posi == 0)) {
rowdata.cb.setVisibility(View.INVISIBLE);
rowdata.sw.setVisibility(View.INVISIBLE);
rowdata.subpref.setVisibility(View.INVISIBLE);
} else {
rowdata.cb.setVisibility(View.VISIBLE);
rowdata.sw.setVisibility(View.VISIBLE);
rowdata.subpref.setVisibility(View.VISIBLE);
}
rowdata.cb.setTag(posi);
rowdata.cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int posi = (Integer) buttonView.getTag();
if (!_isFromView) {
_listState.get(posi).setSelected(isChecked);
}
}
});
rowdata.sw.setTag(posi);
rowdata.sw.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isOnOffed) {
int posi = (Integer) buttonView.getTag();
if (!_isFromView) {
_listState.get(posi).setOnOffed(isOnOffed);
}
}
});
return convertView;
}
@Override
public int getCount() {
return this._listState.size();
}
@Override
public RowData getItem(int posi) {
if( posi < 1 ) {
return null;
}
else {
return this._listState.get(posi-1);
}
}
@Override
public long getItemId(int posi) {
return 0;
}
private class RowDataHolder {
public TextView pref;
public TextView subpref;
public CheckBox cb;
public Switch sw;
}
}
private class ClickListener implements View.OnClickListener {
CustomSpinnerAdapter _spinneradapter;
ClickListener(CustomSpinnerAdapter spinneradapter) {
this._spinneradapter = spinneradapter;
}
@Override
public void onClick(View view) {
TextView tv;
tv = findViewById(R.id.textView0);
int num = Integer.valueOf(tv.getText().toString()) + 1;
String pref = this._spinneradapter.getItem(num).getPref();
String subpref = this._spinneradapter.getItem(num).getSubPref();
Boolean sw = this._spinneradapter.getItem(num).isOnOffed();
Boolean cb = this._spinneradapter.getItem(num).isSelected();
tv = findViewById(R.id.textView2);
tv.setText(pref);
tv = findViewById(R.id.textView4);
tv.setText(subpref);
tv = findViewById(R.id.textView6);
tv.setText(sw?"true":"false");
tv = findViewById(R.id.textView8);
tv.setText(cb?"true":"false");
}
}
}
RowData.java
package org.sibainu.relax.room.last2;
public class RowData {
// メンバー
private String _pref;
private String _subpref;
private boolean _selected;
private boolean _onoffed;
public void setKen(String ken) {
String[] _ken = ken.split("-");
if (_ken.length != 2) {
this._pref = "";
this._subpref = "";
}
else {
this._pref = _ken[0];
this._subpref = _ken[1];
}
}
public String getPref() {
return this._pref;
}
public String getSubPref() { return this._subpref;}
public boolean isSelected() {
return this._selected;
}
public void setSelected(boolean selected) {
this._selected = selected;
}
public boolean isOnOffed() {
return this._onoffed;
}
public void setOnOffed(boolean onoffed) {
this._onoffed = onoffed;
}
}
res/drawable
角ばったボーダーではなく丸めています。
rounded_border.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:layout_width="match_parent" android:layout_height="match_parent" android:shape="rectangle">
<corners android:radius="10dp" />
<stroke android:width="2dp" android:color="#2E2E2E" />
</shape>
</item>
</selector>
スピナーも丸めています。
spinner_border.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape ="rectangle">
<corners android:radius="10dp" />
<solid android:color="#FFFFFFFF"/>
<padding
android:top="10dp"
android:bottom="10dp"
android:left="10dp"
android:right="10dp" />
<stroke
android:width="2dp"
android:color="#808080" />
</shape>
activity_main.xml
View が多いのでコードも多くなりました。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.sibainu.relax.room.last2.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text=""
android:textAlignment="center"
android:textColor="#000000"
android:textSize="30sp" />
<LinearLayout
android:id="@+id/gidline"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="@drawable/rounded_border"
android:orientation="horizontal">
<EditText
android:layout_width="599dp"
android:layout_height="56dp"
android:layout_weight="2"
android:background="@null"
android:paddingHorizontal="16dp"
android:text="" />
<View
android:layout_width="2dp"
android:layout_height="match_parent"
android:background="#2E2E2E" />
<Spinner
android:id="@+id/pre_spinner"
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_marginEnd="5dp"
android:background="@drawable/spinner_border"
android:spinnerMode="dropdown" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/button0"
android:layout_width="150dp"
android:layout_height="50dp"
android:text="Button" />
<TextView
android:id="@+id/textView0"
android:layout_width="150dp"
android:layout_height="50dp"
android:gravity="center"
android:text="5" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/textView1"
android:layout_width="150dp"
android:layout_height="50dp"
android:gravity="center"
android:text="pref" />
<TextView
android:id="@+id/textView2"
android:layout_width="150dp"
android:layout_height="50dp"
android:gravity="center"
android:text="" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/textView3"
android:layout_width="150dp"
android:layout_height="50dp"
android:gravity="center"
android:text="subpref" />
<TextView
android:id="@+id/textView4"
android:layout_width="150dp"
android:layout_height="50dp"
android:gravity="center"
android:text="" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<TextView
android:id="@+id/textView5"
android:layout_width="150dp"
android:layout_height="50dp"
android:gravity="center"
android:text="switch" />
<TextView
android:id="@+id/textView6"
android:layout_width="150dp"
android:layout_height="50dp"
android:gravity="center"
android:text="" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/textView7"
android:layout_width="150dp"
android:layout_height="50dp"
android:gravity="center"
android:text="check" />
<TextView
android:id="@+id/textView8"
android:layout_width="150dp"
android:layout_height="50dp"
android:gravity="center"
android:text="" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
デザインエディターで見る
メイン画面はこんな感じです。

item.xml
スピナーのドロップダウンの書式です。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/prefTV"
android:layout_width="200dp"
android:layout_height="50dp"
android:layout_alignParentStart="true"
android:layout_marginStart="27dp"
android:gravity="center"
android:text="pref"
android:textAlignment="gravity" />
<CheckBox
android:id="@+id/checkbox"
android:layout_width="170dp"
android:layout_height="50dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="11dp"
android:layout_toEndOf="@+id/switch1" />
<Switch
android:id="@+id/switch1"
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_marginStart="14dp"
android:layout_toEndOf="@+id/subprefTV"
android:text="Switch" />
<TextView
android:id="@+id/subprefTV"
android:layout_width="240dp"
android:layout_height="50dp"
android:layout_alignParentBottom="false"
android:layout_marginStart="20dp"
android:gravity="center"
android:layout_toEndOf="@+id/prefTV"
android:text="subpref" />
</RelativeLayout>
デザインエディターで見る
こんな感じの行表示となります。

タブレットの実行画面
左が起動直後のタブレットの画面です。
右がスピナーになっいる「県・令制国一覧」のボタンをタップしてドロップ画面が表示された画面です。

左がドロップ画面のスウィッチ・チェックを操作した画面です。
右がドロップ画面が閉じて「ボタン」をタップして5番目の入力したデータを表示した画面です。

使い道はないのですが、こんなこともできるということで作ってみました。
ここまでとします。