CheckedTextViewを知り、ListViewを知る


CheckedTextViewをご存知でしょうか。
私は先日、Android Studioの予測機能で存在を知りました。
ということで、この機会にCheckedTextViewを使えるようになりたいと思います。

◯実行環境
Android Studio：4.2.2
Kotlin：1.3.72

##CheckedTextViewとは
TextViewのサブクラスに位置する、チェック判定機能がついたTextViewのことです。
ListViewと共に使われるもので、ListViewの設定において`setChoiceMode`が`CHOICE_MODE_NONE`でない場合、つまりListViewの各項目が選択可能な場合に使用することができます。
ちなみにですが、ListViewを実装せずに個別で使うこともできました。
特段思いつく用途はありませんが、何かの際に役立つかも知れませんので覚えておこうと思います。

また、CheckedTextViewではチェック判定を可視化するために`setCheckMarkDrawable`という機能を用います。
よく見かけるCheckBoxクラスでは左側にチェックボックス、その右側にテキストという並びです。一方、CheckedTextViewでCheckMarkを設定した場合は左側にテキスト、右側にチェックマークが配置されます。
![CheckBoxとCheckedTextView.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396691/594ea0d3-eb2a-a0bc-1a15-8d8b460d6890.png)

CheckMarkはdrawable階層から静止画を選択して設定するため、事前にアイコンなどを読み込んでおくことをおすすめします。
手軽に行う場合には、Android SDKやmaterialデザインのライブラリに用意されているアイコンをVectorDrawableとして設定するのが良いと思います。
また、CheckMarkを設定しない場合はTextViewと同じように使用することができます。

今回はListViewも初めての実装となるため、簡単にListViewについてもまとめました。

##ListViewの実装
###ListViewとは
その名の通り、リストを表示させるためのViewクラスです。

似たようなクラスとしてrecyclerViewがありますが、
「とにかく手軽に、垂直方向に要素を並べて表示したい！」という方はListviewを、
「機能や表示をカスタマイズしてより柔軟に実装したい！」という方はrecyclerViewを使用するのがおすすめです。

###ListViewの使い方

手順は以下の通りです。
①xmlファイルでListViewを定義する
②リスト項目を設定する
③activity_main.xmlに設定したListViewを読み込む
④adapterを用意し、リスト項目とListViewを対応付ける

```activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
   
    <!-- ①xmlファイルでListViewを定義 -->
    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
```
```kotlin:listItem.xml
<resources>
    <string-array name="nutrients">
        <item>タンパク質</item>
        <item>脂質</item>
        <item>飽和脂肪酸</item>
        ...
    </string-array>
</resources>
```

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ②リスト項目を設定
        val itemList = resources.getStringArray(R.array.nutrients)

        // ③activity_main.xmlに定義したListViewを読み込む
        val listView = findViewById<ListView>(R.id.list_view)

        // ④ArrayAdapterを用意し、リスト項目とListViewを対応付ける
        // ArrayAdapterではcontext、1項目分のレイアウトファイル、項目を定義した配列を指定する
        val adapter: ArrayAdapter<Any> = ArrayAdapter(this, R.layout.item, itemList)
        listView.adapter = adapter
    }
}
```
今回はリスト項目を設定するためにresourcesファイルを作成し、string-arrayとして設定しました。
そのほかにも様々な実装方法がありますので忘れないように記載しておきます。

```kotlin
//arrayを設定する方法
val itemList = arrayOf( "aaa", "bbb", "ccc", ...)

//for文を使って設定する方法
val itemList = arrayListOf<Any>()
for(i in 0..10) {itemList.add("${i}番")}
```

また、Android SDKには1項目分のレイアウトファイルとして、`simple_list_item_1.xml`が用意されています。
そのため、自分で用意せずにそちらを使用することも可能です。


以上で基本的なListViewが実装できました。

##CheckedTextViewの実装
ここからは、本題のCheckedTextViewの実装と、それに合わせたListViewの修正を行います。

現在、1項目分のレイアウトファイルとして指定したitem.xmlは以下のようになっています。

```kotlin:item.xml
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView
    android:id="@+id/checked_text_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:textSize="20sp"
    android:checked="false" //checkステータスを設定
    android:checkMarkTint="@color/teal_700" //checkMarkの色を指定
    android:checkMark="@drawable/ic_android_black_24dp" //checkMarkを指定
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    />
```

このCheckedTextViewを使用して、以下のような機能を実装したいと思います。
各項目をクリックすることでその項目のAndroidマークとチェックマークが入れ替わる機能です。
![前後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396691/856a1c3f-294f-dde0-1cf9-1e0c98d84fcf.png)


手順は以下の通りです。
①カスタムAdapterを作成し、getView内にクリックリスナーを設置する
`BaseAdapter`を元にして作成するため、`BaseAdapter`に必須の以下の4つの要素を含める必要があります。

- getCount：リストの要素数を数える
- getItem：position位置の要素を取得する
- getItemId：position位置の要素IDを取得する
- getView：Viewを生成する

②先ほど`ArrayAdapter`を使用して実装した部分をカスタムAdapterに変更する

```kotlin:MyAdapter.kt
class MyAdapter(
    private val context: Context,
    private var itemList: Array<String>
) : BaseAdapter() {

    override fun getCount(): Int {
        return itemList.size
    }

    override fun getItem(position: Int): Any {
        return itemList.get(position)
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    @SuppressLint("ViewHolder")
    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        val inflater = LayoutInflater.from(context)
        val convertView = inflater.inflate(R.layout.item, parent, false)
        val checkedTextView = convertView.findViewById<CheckedTextView>(R.id.checked_text_view)
        
        //viewに要素を設定
        checkedTextView.setText(itemList[position])
       
        // ①viewにクリックリスナーを設定
        checkedTextView.setOnClickListener {
            if (checkedTextView.isChecked) {
                //押し直した時にAndroidのマークになるように設定
                checkedTextView.setCheckMarkDrawable(R.drawable.ic_android_black_24dp)
                checkedTextView.isChecked = false
            } else {
                //1回押した時にチェックマークが出るように設定
                checkedTextView.setCheckMarkDrawable(R.drawable.ic_baseline_check_24)
                checkedTextView.isChecked = true
            }
        }
        return checkedTextView
    }
}
```

```kotlin:MainActivity.kt
val adapter: BaseAdapter = MyAdapter(this, itemList)// ②カスタムAdapterを読み込むように修正
```

以上で各要素をクリックした際にチェックマークが動的に変更される機能が実装できました。

##まとめ：値の受け渡し方に工夫が必要
今回は結構な時間をかけてカスタムAdapterの実装を行いましたが、その後の調査でArrayAdapterを使用しても、今回のような動的にレイアウトを変更するクリックリスナーの実装ができそうだと分かりました。再度調べて実装してみようと思います。

また、今回の実装では、スクロールによる画面外への移動でクリック状態が初期値に戻ってしまいます。この機能をアプリなどに組み込む場合には、画面外に移動した場合にも値が保存される必要があります。この点については次回まとめられたらと思います。

ArrayAdapterを使用した場合のクリックリスナーの実装やAdapterの使い分けについて知見やおすすめ記事情報をお持ちの方は、ぜひコメントをお願いいたします。
##参考
[Developers：Android ListView](https://developer.android.com/reference/android/widget/ListView)
[【Android】ListViewを使うための基礎知識(1) - Tumbling Dice](https://outofmem.hatenablog.com/entry/2014/10/29/040510)
[Developers：CheckedTextView](https://developer.android.com/reference/android/widget/CheckedTextView#setCheckMarkTintList(android.content.res.ColorStateList))
[9 thoughts on “CheckedTextView Tutorial With Example In Android Studio”](https://abhiandroid.com/ui/checkedtextview)
[[Android] BaseAdapterで画像とテキストをListView表示](https://akira-watson.com/android/baseadapter.html)
[Android-ListView(BaseAdapter)](http://mitoroid.com/category/android/android_sample_listview_base.php)
[Android Open Source Project](https://github.com/aosp-mirror)