Sibainu Relax Room

愛犬の柴犬とともに過ごす部屋

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

朝の散歩です。すがすがしい顔している柴犬です。

鳩は餌を探しているのか、怒っているのか分かりませんがアクションが激しい動きをしていました。

これでもかというくらい枯葉をかき回していました。距離にして足元から2m位のところでした。

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 に渡します。

copy

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

copy

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

copy

<?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

copy

<?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 が多いのでコードも多くなりました。

copy

<?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

スピナーのドロップダウンの書式です。

copy

<?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番目の入力したデータを表示した画面です。

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

ここまでとします。