RecyclerViewと仲良くなる
いや今更かよっていう話ではある
しかしながら、私はRecylerViewといつまで経っても仲良くなれず
非常に悶々とした日々をすごした過去を持つ
たまたま久々にAndroidを触り、RecyclerViewと戯れる機会を得たので
真摯に向き合ってみると案外難しくはなかった
てことでちょこっとだけ説明を加えたSample
ゴール
今回はTODOリストもどきを作っていく
アプリを閉じると内容は削除されるし、ソートとかもないので
本当にガワだけである
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.xml
とic_remove_black_24dp.xml
(File -> New -> Vector Asset
で追加できる)
3.Itemのレイアウトを作成
続いて一行一行をレイアウトするためにファイルを作成する
res > layout
にitem.xml
を作成
レイアウトを組んでいく
今回必要なのは
- EditTextに入力された内容を表示するTextView
- 作成日時を表示するTextView
- 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
RecyclerView
はAdapter
にかかっていると言っても過言ではない
RecyclerView
に関する操作は全てAdapter
で行う
某所でAdapter
はRecyclerView
にデータを渡す橋渡し役だと習った
そんな橋渡し役のコードがこちら
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と仲良くなれなかった一番最初の原因は
クラスを書いてる間最初しばらくはエラーが出ること
以下の順番で書くとよい
class ~~: RecyclerView.Adapter<~~.ViewHolder>() {}
を書く- ViewHolderをぱぱっと書く(kotlin-android-extensionsのお陰で簡単に書けるようになった)
onCreateViewHolder
とonBindViewHolder
をoverrideする- あとは適当に書いていく
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は基本閉じてます)