【Android】Pinterest風なGridViewを作る

## 00.経緯
仕事で「リストの表示をPinterrest風にしたい」という要望がありました。
雑に「android pinterest layout」とググってみたところ日本語のページが引っかからなかったので、メモしておきます。
なお、これを書いている際になんとなく順番を入れ替えて「pinterest layout android」でググってみたところ以下が引っかかりました。

* [要素の高さがバラバラのRecyclerViewを使う(Pinterest的レイアウト)](https://qiita.com/YS-BETA/items/61ddda89c389f09fce93)

なんでや

## 01. Gradleへの変更
RecyclerViewを使用するので以下を追加します。

```gradle:build.gradle
dependencies {
    implementation "androidx.recyclerview:recyclerview:1.1.0"
}
```

## 02. Gridに表示するViewのレイアウトを作成
高さを可変(wrap_content)にしておくことが重要です。

```xml:view_masonry_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#00ffff"
        android:gravity="center"
        android:padding="5dp" />
</LinearLayout>
```

## 03. ViewHolderの作成
[RecyclerView#ViewHolder](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.ViewHolder)を継承したViewHolderを作成します。

```kotlin:MasonryViewHolder
class MasonryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    private var imageView: ImageView = itemView.findViewById(R.id.image_view)
    private var textView: TextView = itemView.findViewById(R.id.text_view)

    fun setImage(@DrawableRes resId: Int) {
        imageView.setImageResource(resId)
    }

    fun setText(text: String) {
        textView.text = text
    }
}
```

## 04. Adapterの作成
[RecyclerView#Adapter](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter)を継承した(ry

```kotlin:MasonryAdapter
class MasonryAdapter(context: Context, private val items: List<Pair<Int, String>>): RecyclerView.Adapter<MasonryViewHolder>() {

    private val inflater: LayoutInflater = LayoutInflater.from(context)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MasonryViewHolder {
        val view = inflater.inflate(R.layout.view_masonry_item, parent, false)
        return MasonryViewHolder(view)
    }

    override fun getItemCount(): Int {
        return items.count()
    }

    override fun onBindViewHolder(holder: MasonryViewHolder, position: Int) {
        holder.setImage(items[position].first)
        holder.setText(items[position].second)
    }
}
```

## 05. RecyclerViewにLayoutManager及びAdapterを適用する
LayoutManagerに[StaggeredGridLayoutManager](https://developer.android.com/reference/androidx/recyclerview/widget/StaggeredGridLayoutManager)を使用します。
※ createListItems()は表示要素の配列を作成しているだけです。

```kotlin
recycler_view.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
recycler_view.adapter = MasonryAdapter(this, createListItems())
```
<img width=50% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235452/b57bf5bf-ad4d-977e-14c4-ad993321f000.png" />

# 06. 要素間にスペースを設ける
ここまででPinterest風なGridViewにはなっていますが、要素間にスペースがありません。
要素のViewにスペースを設けても良いですが、ここは[RecyclerView#ItemDecoration](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.ItemDecoration)を使用してみます。

```kotlin:MasonryItemDecoration
class MasonryItemDecoration : RecyclerView.ItemDecoration() {
    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        outRect.apply {
            left = 16
            right = 16
            bottom = 16
            // 2重にスペースを設けてしまうので、最上段のみ上部にスペースを設けるようにする
            if (parent.getChildAdapterPosition(view) <= 1) {
                top = 16
            }
        }
    }
}
```

```kotlin
recycler_view.addItemDecoration(MasonryItemDecoration())
```

<img width=50% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235452/5985a419-385b-0d9a-6836-df575123d891.png" />

## 07. 要素へのタップを可能にする
リストが表示されればOKということはそうそうないでしょうから、要素をタップできるようにしておきます。
Adapterを少しいじります。
※ 前述部分と変更箇所のみ記載

```kotlin:MasonryAdapter
class MasonryAdapter(context: Context, private val items: List<Pair<Int, String>>): RecyclerView.Adapter<MasonryViewHolder>() {
    interface MasonryItemClickListener {
        fun onClick(position: Int)
    }

    var masonryItemClickListener: MasonryItemClickListener? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MasonryViewHolder {
        val view = inflater.inflate(R.layout.view_masonry_item, parent, false)
        val holder = MasonryViewHolder(view)
        holder.itemView.setOnClickListener {
            masonryItemClickListener?.onClick(holder.adapterPosition)
        }
        return holder
    }
}
```

```kotlin
recycler_view.adapter = MasonryAdapter(this, createListItems()).apply {
    masonryItemClickListener = object : MasonryAdapter.MasonryItemClickListener {
        override fun onClick(position: Int) {
            Toast.makeText(this@MainActivity, "click position: $position", Toast.LENGTH_SHORT).show()
        }
    }
}
```

<img width=50% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235452/1b526270-7f72-ce65-c897-638183d03d3f.png" />

## 98. 参考
* [Pinterest Masonry Layout (Staggered Grid)](https://blog.iamsuleiman.com/pinterest-masonry-layout-staggered-grid/)
* [RecyclerView のクリックをどこで処理するか](http://y-anz-m.blogspot.com/2016/11/recyclerview.html)

## 99. サンプルにて使用した画像入手先
* [Placehold.jp](https://placehold.jp/)