"Simple" is "Best"

開発時に詰まったところや 調べた結果日本語での情報が無かったり古かったりした場合に自分用のメモとして高校生(?)が更新していくブログ

RecyclerViewと仲良くなる

いや今更かよっていう話ではある

しかしながら、私はRecylerViewといつまで経っても仲良くなれず

非常に悶々とした日々をすごした過去を持つ

たまたま久々にAndroidを触り、RecyclerViewと戯れる機会を得たので

真摯に向き合ってみると案外難しくはなかった

てことでちょこっとだけ説明を加えたSample

ゴール

今回はTODOリストもどきを作っていく

f:id:bigbuddha:20181004210318j:plain

アプリを閉じると内容は削除されるし、ソートとかもないので

本当にガワだけである

EdiTextに入力された内容がRecyclerViewに表示される流れに重きを置く

右側のボタンを押すとEditTextの内容が下のRecyclerViewに表示される

Item(一行)の右側のボタンを押すとそのItemが削除される

下準備

とりあえずプロジェクトを新規作成

minSdkは23、targetSdkは27

1.Android Support Design Library

そしてbuild.gradleにADSLを追加

RecyclerView単体もあるが、TextInputLayoutだったりSnackbarを使いたかったので

implementation 'com.android.support:design:27.1.1'

2.ボタン素材(Googleに感謝)

続いて素材を追加

AndroidStudioからGoogleが公開しているMatrial Iconが利用できるので

+-Vectorで取ってくる

ファイル名はic_add_black_24dp.xmlic_remove_black_24dp.xml

(File -> New -> Vector Assetで追加できる)

3.Itemのレイアウトを作成

続いて一行一行をレイアウトするためにファイルを作成する

res > layoutitem.xmlを作成

レイアウトを組んでいく

今回必要なのは

  1. EditTextに入力された内容を表示するTextView
  2. 作成日時を表示するTextView
  3. RemoveをするImageButton

以上3つ

以下そのレイアウト

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <TextView
        android:id="@+id/tv_name"
        android:layout_height="match_parent"
        android:layout_width="0dp"
        android:layout_weight="4"
        android:textSize="18sp"
        android:textColor="#000"
        android:gravity="center|start"
        android:paddingStart="4dp" />

    <TextView
        android:id="@+id/tv_created_at"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:text="XXXX/XX/XX"
        android:textSize="12sp"
        android:gravity="bottom|end"
        android:paddingStart="8dp"
        android:paddingEnd="24dp"
        android:paddingBottom="16dp"/>

    <ImageButton
        android:id="@+id/bt_remove_item"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:src="@drawable/ic_remove_black_24dp"
        android:background="#000f"
        android:layout_gravity="center" />

</LinearLayout>

各値はお好みで

ここで特徴なのが親のLinearLayoutの高さが固定であるということ

wrap_contentでも良いのだが、Buttonが小さくなって誤タップが生じてしまうため

ある程度の高さを用意してあげる

ImageButtonは背景をRGBAで透明にしてあげる

本番

Adapter

RecyclerViewAdapterにかかっていると言っても過言ではない

RecyclerViewに関する操作は全てAdapterで行う

某所でAdapterRecyclerViewにデータを渡す橋渡し役だと習った

そんな橋渡し役のコードがこちら

package net.oldbigbuddha.sample.sample_recyclerview

import android.content.Context
import android.support.v7.widget.RecyclerView
import android.text.format.DateFormat
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import kotlinx.android.synthetic.main.item.view.*

class ItemAdapter(
        private val mItems:   ArrayList<Item>,
        private val mContext: Context
): RecyclerView.Adapter<ItemAdapter.ViewHolder>() {


    override fun getItemCount(): Int = mItems.size

    // Itemを追加する
    fun addItem(item: Item) {
        mItems.add(item)
        notifyDataSetChanged() // これを忘れるとRecyclerViewにItemが反映されない
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false))

    // Itemを削除する
    private fun removeItem(position: Int) {
        mItems.removeAt(position)
        notifyItemRemoved(position)
        notifyDataSetChanged() // これを忘れるとRecyclerViewにItemが反映されない
    }


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {

        holder.tvName.text      = mItems[position].mName

        // reference: Y.A.M の 雑記帳: SimpleDateFormat ではなく android.text.format.DateFormat を使おう - http://bit.ly/2OybKLu
        holder.tvCreatedAt.text = DateFormat.format("yyyy/MM/dd kk:mm:ss", mItems[position].mCreatedAt).toString()

        holder.btRemoveItem.setOnClickListener {
            removeItem(position)
        }

    }

    class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
        val tvName:       TextView    = view.tv_name
        val tvCreatedAt:  TextView    = view.tv_created_at
        val btRemoveItem: ImageButton = view.bt_remove_item
    }
}

RecyclerViewの特徴の1つに必ずViewHolderを実装しないといけないというものがある

ViewHolder is 何 という方はここでは詳しく説明しないのでググっろう

findViewById()は結構実行コストが高いので、一度findしてきたものを保持しておく

だからHolderにViewを入れておく

そんな感じ

私がRecyclerViewと仲良くなれなかった一番最初の原因は

クラスを書いてる間最初しばらくはエラーが出ること

以下の順番で書くとよい

  1. class ~~: RecyclerView.Adapter<~~.ViewHolder>() {}を書く
  2. ViewHolderをぱぱっと書く(kotlin-android-extensionsのお陰で簡単に書けるようになった)
  3. onCreateViewHolderonBindViewHolderをoverrideする
  4. あとは適当に書いていく

Ctrl-OでOverrideできるメソッド一覧がでるので、活用すると良い

notifyDataSetChanged()を忘れると更新されないので注意(これ結構やられる)

MainActivity

あとはひょろひょろ〜と書いていく

  • ちゃんとToolbarを設定すること
  • RecyclerViewにAdapterを設定すること

この2つを忘れなければハマることはない(はず)

 package net.oldbigbuddha.sample.sample_recyclerview

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.widget.LinearLayoutManager
import android.text.TextUtils
import kotlinx.android.synthetic.main.activity_main.*
import java.util.ArrayList

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Set Toolbar
        setSupportActionBar(my_toolbar)
//        my_toolbar.inflateMenu(R.menu.menu_main)

        // Initialize RecyclerView
        recycler_items.layoutManager = LinearLayoutManager(this)

        // Initialize Adapter
        val adapter = ItemAdapter(ArrayList(), this)

        bt_add_item.setOnClickListener {
            if ( !TextUtils.isEmpty( et_item.text.toString() ) ) {

                val task = Item( mName = et_item.text.toString() )
                adapter.addItem(task)

            } else {

                Snackbar.make(container, "Task is empty", Snackbar.LENGTH_SHORT).show();

            }

            et_item.setText("")

        }

        recycler_items.adapter = adapter

    }
}

Toolbarに色々仕込みたい場合はsrc > menuを作って

よしなに書いてinflateしてあげれば良い

ちゃんとEditTextの中身は検査しよう

これで無事動く

最後に

今回のサンプルコードはここからまとめて見れます

たまに更新するかもしれない

もしわからなかったらTwitter@OJI_1941にメンション投げてください

すぐ食いつきます(すみませんDMは基本閉じてます)