【AndroidStudio】独自アダプタとクラスを作成してListViewに一覧を表示する

#はじめに
AndroidでListViewに何かを表示するときは基本的にAdapterを利用しますが、初めは使い方がわからず、とりあえず教本などに書いてあるSimpleAdapterを使いがちな気がします。まあそれでもいいんですが、SimpleAdapterはリストに入ったデータを取り出すのが結構面倒だったので、BaseAdapterを継承してAdapterを自作してしまうという感じです。なにやら難しそうに聞こえますが、やってることは案外単純なので、ListViewで詰まっている方はぜひ。ちなみにJavaしか書けない人間なので、Javaで書いちゃいます。

#ListViewに表示するデータのクラスを作成する
まずは、リストに入れるデータをクラスで定義しましょう。ここでListなどを使ってしまうと、データの変更などが結構面倒です。初心者の方はクラスに抵抗があるかと思いますのでゆっくり説明していきます。
今回は定食屋のメニューをリスト表示してみましょう。MainActivityとは別ファイルでItemというクラスファイルを作ります。

```Java:Item.java
public class Item{
    //フィールド変数
    private int itemId;    //メニューのID
    private String itemName;    //メニューの名前
    private int itemPrice;    //メニューの値段
    private int itemQuantity;    //メニューの注文個数

    //コンストラクタ（インスタンス化するときにID、名前、値段を受け取って設定します）
    public Item(int itemId, String itemName, int itemPrice){
        setItemId(itemId);
        setItemName(itemName);
        setItemPrice(itemPrice);
        setItemQuantity(0);
    }

    //セッター
    public void setItemId(int itemId){
        this.itemId = itemId;
    }
    public void setItemName(String itemName){
        this.itemName = itemName;
    }
    public void setItemPrice(int itemPrice){
        this.itemPrice = itemPrice;
    }
    public void setItemQuantity(int itemQuantity){
        this.itemQuantity = itemQuantity;
    }

    //ゲッター
    public int getItemId(){
        return itemId;
    }
    public String getItemName(){
        return itemName;
    }
    public int getItemPrice(){
        return itemPrice;
    }
    public int getItemQuantity(){
        return itemQuantity;
    }
}
```
とりあえずは最低限、ID、名前、値段、注文個数を作って、コンストラクタとゲッターセッターを定義しておきましょう。

#Layoutのファイルを作成する
次はactivity_main.xmlを編集してListViewを作りましょう。

```xml:activity_main.xml
<LinearLayout
    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"
    tools:context=".MainActivity"
    android:orientation="vertical">
    
    <ListView
        android:id="@+id/lvItems"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>
 
```
LinearLayoutのxmls:～の3行は、このままコピーするとうまく動かない場合があるので、最初から入力されているものを流用し、ConstraintLayoutをLinearLayoutに変えるのと、orientationを書くだけの方がいいかもしれません。ちなみに閉じタグがうまく変わってくれないことがあるので、一番後ろのタグをLinearLayoutに変えるのをお忘れなく、、、。

次はこのListView一行ごとのレイアウトを作ります。row.xmlをres/layoutの中に作成し、好きなレイアウトで作ってください。注文数を減らしたり増やしたりしたいので、+-ボタンと数を表示するTextViewは作りましょう。また、文字の表示はres/valuesの中にあるString.xmlを編集して必ずそこから持ってくるようにしましょう。

```xml:string.xml
<resources>
    <string name="app_name">ListViewSample</string>
    <string name="btPlus">+</string>
    <string name="btMinus">-</string>
</resources>
```

```xml:row.xml
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="0.6"
        android:orientation="vertical">
        <TextView
            android:id="@+id/tvItemName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
        <TextView
            android:id="@+id/tvItemPrice"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="0.4"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btItemMinus"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="0.3"
            android:text="@string/btMinus"/>
        <TextView
            android:id="@+id/tvItemQuantity"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="0.4"
            android:gravity="center"/>
        <Button
            android:id="@+id/btItemPlus"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="0.3"
            android:text="@string/btPlus"/>
    </LinearLayout>

</LinearLayout>
```
レイアウトよくわからんという方は、上のソースをコピペしましょう。

#アダプタを作成する
リストに入れるメニューのクラスやリストの場所、リスト一行一行のレイアウトは作ったので、これらを紐づける、今回の主役であるAdapterを作成していきます。書いてみると意外とやってることは単純です。自作Adapterを作成するときには、BaseAdapterというものを継承します。ItemAdapterというクラスをItemAdapter.javaファイルを作成して書いていきましょう。インポートはここには記載しないので、赤文字になったところにカーソルを持っていくと解決策みたいなのが出てくるので、import～って書いてあるところをクリックすれば勝手にインポートしてくれます。

```java:ItemAdapter.java
public class ItemAdapter extends BaseAdapter {
    //フィールド変数
    private final Item[] itemList;
    private final LayoutInflater inflater;

    //コンストラクタは、MainActivityのコンテキストと、定食のリストを配列で受け取ります
    public ItemAdapter(Context context, Item[] itemList){
        this.itemList = itemList;
        this.inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        //リストに表示するデータの個数です
        return itemList.length;
    }

    @Override
    public Item getItem(int position) {
        //引数で指定された位置にある定食の情報を返します
        return itemList[position];
    }

    @Override
    public long getItemId(int position) {
        //引数で指定された位置にある定食のIDを返します
        return itemList[position].getItemId();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //このメソッドで一行ずつ定食の情報を紐づけていきます
        Item item = itemList[position];     //定食の配列の先頭からひとつずつ定食を取り出します

        if(convertView == null){
            convertView = inflater.inflate(R.layout.row, null);
        }
        //rowの定食名、値段、個数を入れるオブジェクトをJavaに取り出してきます
        TextView tvItemName = convertView.findViewById(R.id.tvItemName);
        TextView tvItemPrice = convertView.findViewById(R.id.tvItemPrice);
        TextView tvItemQuantity = convertView.findViewById(R.id.tvItemQuantity);

        //オブジェクトに文字をセットします。文字列しか入れられないので、数字は文字列型にキャストして渡します。
        tvItemName.setText(item.getItemName());
        tvItemPrice.setText(item.getItemPrice() + "円");
        tvItemQuantity.setText(String.valueOf(item.getItemQuantity()));

        //+、-ボタンの処理を書きます
        Button btItemPlus = convertView.findViewById(R.id.btItemPlus);
        Button btItemMinus = convertView.findViewById(R.id.btItemMinus);
        if(item.getItemQuantity() <= 0){
            btItemMinus.setEnabled(false);      //注文個数が0個なら、-ボタンを押せなくします
        }
        btItemPlus.setOnClickListener(new View.OnClickListener() {  //分かりやすいように、ラムダ式をあえて避けました
            @Override
            public void onClick(View v) {
                item.setItemQuantity(item.getItemQuantity() + 1);       //個数を足してます
                btItemMinus.setEnabled(true);       //個数が増えたら-ボタンを押せるようにします
                tvItemQuantity.setText(String.valueOf(item.getItemQuantity()));     //個数を増やしたらそれを表示します
            }
        });
        btItemMinus.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(item.getItemQuantity() >= 1){
                    item.setItemQuantity(item.getItemQuantity() - 1);      //個数を減らしています
                }
                if(item.getItemQuantity() < 1){         //個数が1より小さい、つまり0個になったら-ボタンを押せなくします
                    btItemMinus.setEnabled(false);
                }
                tvItemQuantity.setText(String.valueOf(item.getItemQuantity()));
            }
        });

        return convertView;
    }
}
```
これで部品がすべてそろったので、MainActivityでこれらを利用してプログラムを書いていきます。

#MainActivityの編集
とはいっても、ほとんどの処理をそれぞれのクラスに書いてあるので、MainActivityでは引数を渡していくだけです。早速やってみましょう。こちらもインポート文は省きます。

```java:MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ListView lvItems = findViewById(R.id.lvItems); 
        //先ほど作ったItemクラスを使って、Item配列に定食のデータを入れていきます
        Item[] itemList = {
                new Item(0, "生姜焼き定食", 700),
                new Item(1, "焼き魚定食", 800),
                new Item(2, "焼肉定食", 800),
                new Item(3, "刺身定食", 1000),
                new Item(4, "メンチカツ定食", 850),
                new Item(5, "ロースかつ定食", 1000),
                new Item(6, "日替わり定食", 650)
        };

        //アダプターを作成し、ListViewに反映させます
        ItemAdapter itemAdapter = new ItemAdapter(MainActivity.this, itemList);
        lvItems.setAdapter(itemAdapter);
    }

}
```
これでアプリを起動してみましょう。各行に定食のデータが表示され、ボタンを押すと数が増減するかと思います。個数のデータを取り出す方法はとても簡単なので説明します。

#個数などのデータを取り出す
今回のプログラムで、最終的に定食のデータを持っているのはAdapterです。なので個数などのデータを取り出したり変更したい時には、Adapterにアクセスする必要があります。試しに、itemIdが3の刺身定食のデータを取り出して表示してみましょう。
まずはstring.xmlとactivity_main.xmlを少し改造します。

```xml:string.xml
<resources>
    <string name="app_name">ListViewSample</string>
    <string name="btPlus">+</string>
    <string name="btMinus">-</string>
<!--    ここから下を追記　　-->
    <string name="btGetItem">取得</string>
    <string name="btResetItem">リセット</string>
<!--    ここまで　　-->
</resources>
```


```xml:activity_main.xml
<LinearLayout
    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"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <ListView
        android:id="@+id/lvItems"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

<!--    ここから下を追記　　-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:id="@+id/tvGetItemName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:id="@+id/tvGetItemQuantity"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center">
        <Button
            android:id="@+id/btGetItem"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/btGetItem"
            android:layout_marginEnd="20dp"/>
        <Button
            android:id="@+id/btResetItem"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/btResetItem"/>
    </LinearLayout>
<!--　　ここまで　　-->

</LinearLayout>
```

つぎはMainActivityを改造します。

```java:MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ListView lvItems = findViewById(R.id.lvItems);
        //先ほど作ったItemクラスを使って、Item配列に定食のデータを入れていきます
        Item[] itemList = {
                new Item(0, "生姜焼き定食", 700),
                new Item(1, "焼き魚定食", 800),
                new Item(2, "焼肉定食", 800),
                new Item(3, "刺身定食", 1000),
                new Item(4, "メンチカツ定食", 850),
                new Item(5, "ロースかつ定食", 1000),
                new Item(6, "日替わり定食", 650)
        };

        //アダプターを作成し、ListViewに反映させます
        ItemAdapter itemAdapter = new ItemAdapter(MainActivity.this, itemList);
        lvItems.setAdapter(itemAdapter);

//~~~~~~~~~~~~~~~~~~~~~ここから追記します~~~~~~~~~~~~~~~~~~~~~~~~~
        //刺身定食を取得するボタンの処理
        Button btGetItem = findViewById(R.id.btGetItem);
        btGetItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ItemAdapter adapter = (ItemAdapter) lvItems.getAdapter();   //アダプタをリストビューから取得します。キャストしないとエラーになります
                TextView tvGetItemName = findViewById(R.id.tvGetItemName);
                tvGetItemName.setText(adapter.getItem(3).getItemName());    //配列の4番目の定食の名前を取得して表示(0からなので3で4番目)
                TextView tvGetItemQuantity = findViewById(R.id.tvGetItemQuantity);
                tvGetItemQuantity.setText(String.valueOf(adapter.getItem(3).getItemQuantity())); //名前と同じ
            }
        });

        //刺身定食の個数をリセットするボタンの処理
        Button btResetItem = findViewById(R.id.btResetItem);
        btResetItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ItemAdapter adapter = (ItemAdapter) lvItems.getAdapter();   //アダプタをリストビューから取得します。キャストしないとエラーになります
                adapter.getItem(3).setItemQuantity(0);  //個数を0に変更します
                adapter.notifyDataSetChanged();     //アダプタのデータを書き換えたときは必ずこの処理を行わないと反映されません
            }
        });
//~~~~~~~~~~~~~~~~~~~~ここまで~~~~~~~~~~~~~~~~~~~~~~
    }

}

```
Buttonをインポートしないといけないので、インポートしてあげてください。
これでアプリを起動してみましょう。刺身定食の情報を取得したり、個数をセットすることができるようになりました。実際はこんな使い方しませんが、これを応用していろいろと使っていただければと思います。

#最後に
今回はソースが多く長文になりましたが、やってることはシンプルかなと思います。リストビューはAndroid開発で最初に躓くポイントじゃないかなと個人的には思っているので、アダプタをいろいろいじってみて慣れるのが一番かと思います。
私もまだまだコードに無駄などがあるかと思いますが参考程度にしていただければ幸いです。