LoginSignup
2
2

More than 3 years have passed since last update.

【Android】Pinterest風なGridViewを作る

Posted at

00.経緯

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

なんでや

01. Gradleへの変更

RecyclerViewを使用するので以下を追加します。

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

02. Gridに表示するViewのレイアウトを作成

高さを可変(wrap_content)にしておくことが重要です。

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を継承したViewHolderを作成します。

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を継承した(ry

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を使用します。
※ createListItems()は表示要素の配列を作成しているだけです。

recycler_view.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
recycler_view.adapter = MasonryAdapter(this, createListItems())

06. 要素間にスペースを設ける

ここまででPinterest風なGridViewにはなっていますが、要素間にスペースがありません。
要素のViewにスペースを設けても良いですが、ここはRecyclerView#ItemDecorationを使用してみます。

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
            }
        }
    }
}
recycler_view.addItemDecoration(MasonryItemDecoration())

07. 要素へのタップを可能にする

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

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
    }
}
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()
        }
    }
}

98. 参考

99. サンプルにて使用した画像入手先

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2