0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

男坂Advent Calendar 2020

Day 15

[Android]中央でピタッととまるカルーセルの作り方

Last updated at Posted at 2020-12-14

中央でピタッととまるカルーセルの作り方の解説します。ハマりどころがあるので記事にしました。

実装手順

Step 1. 依存ライブラリの記述

RecyclerViewとGravitySnapHelperというライブラリを使います。build.gradleに以下を記述します。

app/build.gradle
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.github.rubensousa:gravitysnaphelper:2.2.1'

Step 2. 水平方向にスクロール可能なRecyclerViewを追加する

水平方向にスクロール可能なRecyclerViewでカルーセルは大体実現できます。

val carousel = findViewById<RecyclerView>(R.id.carousel)
carousel.apply {
    // RecyclerViewを水平方向にスクロールできるように設定
    layoutManager =
        LinearLayoutManager(this@MainActivity, LinearLayoutManager.HORIZONTAL, false)
    // サンプルAdapter
    adapter = MyAdapter((0..30).map { it.toString() }.toTypedArray()) 

    // カルーセルの両端が中央に表示されるようにRecyclerViewの両端にPaddingを入れる。
    val itemWidthDp = 96 // 各項目の幅。サンプル値。
    val itemWidthPixels = (itemWidthDp * resources.displayMetrics.density  + 0.5).toInt()
    val paddingH = (resources.displayMetrics.widthPixels - itemWidthPixels) / 2
    setPadding(paddingH, 0, paddingH, 0)
    clipToPadding = false
    setHasFixedSize(true)
}

ポイントは2つです。

  1. 左右両端にPaddingを入れつつclipToPaddingをfalseにすることで、左右両端の項目を中央に表示させる
  2. setHasFixedSize(true)を設定する

Web検索をすると、左右両端の項目にItemDecoratorを使って余白を入れる方法をしばしば見かけますが、それだと項目の削除がある場合には上手く対処できません。末尾項目を削除したとき、中間状態として末尾のItemDecoratorが消えます。その状態で表示位置の調整が行われることで、表示位置が不正になる問題が起きます。なお、画面の表示中にカルーセルの項目を変化させないなら、ItemDecoratorの方法も使えます。

setHasFixedSize(true)を設定するのも、カルーセルの項目数が変化したときに表示位置が不正になる問題を回避するのためです1

Step 3. カルーセルの項目が丁度良い位置に止まるようにする

GravitySnapHelperを使います。RecyclerViewに付属しているLinearSnapHelperは、中央に表示されている位置(この記事では以下、Snap位置と呼びます)の変化を検知するAPIがないので不便だからです。

val snapHelper = GravitySnapHelper(Gravity.CENTER, true)
snapHelper.attachToRecyclerView(carousel)

これでカルーセルの項目が丁度良い位置に止まるようになります。

Step 4. Snap位置の変化を検知する

GraivitySnapHelperのAPIに位置変化リスナーがあるので、それを使います。カルーセルの位置をTextViewに表示するサンプルコードを示します。

val textView = findViewById<TextView>(R.id.textView)
textView.text = "0" // 最初はリスナー呼ばれないので、初期表示は自分で設定する必要がある
snapHelper.setSnapListener {
    textView.text = it.toString() // it = Snap位置
}

番外. プログラムからSnap位置を設定する

GraivitySnapHelper#smoothScrollToPositionを使います。ここまでの手順だけだと、各項目をタップしたときにその項目が中央に表示されません。項目のクリックイベントを拾う + smoothScrollToPositionを呼ぶことで、タップした項目を中央に表示できます。

snapHelper.smoothScrollToPosition(position)

smoothでない方のSnap位置設定のAPIもありますが、期待どおりに動作しない場面がしばしばあるので、smoothの方を使うのが無難だと思います(動作しない理由は不明)。

サンプルコード

  1. カルーセルを作る場合、項目のサイズに応じてRecyclerViewの幅が変化することはないと思うので、基本的に設定できると思います。描画パフォーマンスも上がります。

0
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
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?