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番目の入力したデータを表示した画面です。
使い道はないのですが、こんなこともできるということで作ってみました。
ここまでとします。