Upgrade to Pro — share decks privately, control downloads, hide ads and more …

それっぽいカルーセルをつくる

 それっぽいカルーセルをつくる

2018/12/13に開催された Shibuya.apk #30 にて発表した資料です

Yoshihiro WADA

December 13, 2018
Tweet

More Decks by Yoshihiro WADA

Other Decks in Programming

Transcript

  1. それっぽいカルーセルを作る
    Cyber Agent Inc.
    Yoshihiro Wada a.k.a. @e10dokup
    2018/12/13 Shibuya.apk

    View Slide

  2. 自己紹介
    • Yoshihiro Wada a.k.a. @e10dokup
    • 大体右のアイコンで息をしています
    • CyberAgant Inc. / Ameba
    • Amebaブログやってます
    • We’re Hiring
    • カメラ沼
    • 財布の透過率が高いのが最近の悩み

    View Slide

  3. 今日のコンテンツ
    • カルーセルを作る
    • ループするしないとかするとかいろいろあるが…
    • 今回は画面横方向に面が並んだものと定義します
    • 正直EpoxyにCarouselItemってあるやん…?
    2
    1 3 4

    View Slide

  4. 特に…
    • 画面横幅いっぱいにコンテンツアイテムとかを出すスナップで行き来できるカルーセル
    • マージンがついたり
    • 触っていないときは隣の面が見えるように…とかだったり
    • 今回はRecyclerViewで実装していきます(ViewPager)
    • 本音:Groupieで雑にAdapterを作って管理したかった
    2
    1 3

    View Slide

  5. やっていきましょう

    View Slide

  6. レベル1
    単純に画面いっぱいのカルーセル

    View Slide

  7. どうということはない
    • RecyclerViewに対してlayout_widthがmatch_parentなItemのLayoutを用意する
    • Adapterにはそのレイアウトを渡してInflateさせる
    • SnapHelperにRecyclerViewを渡してスナップによるスクロールをさせるようにする

    View Slide

  8. SnapHelper
    • RecyclerView.onFlingListenerを継承したAbstract class
    • フリングしたときに子Viewにスナップさせる
    • 継承クラスは2つ
    • LinearSnapHelper
    • snap対象のViewがRecyclerViewの中心に来るようにスナップ
    • PagerSnapHelper
    • ViewPagerっぽい挙動のスナップ
    • SnapHelper#attachToRecyclerView(recyclerView)でアタッチ
    • nullを入れるとdetach。多重attachしないように気をつける

    View Slide

  9. レイアウトを用意して
    android:layout_width="match_parent"
    android:layout_height="150dp"
    android:background="#EEEEEE">
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    ɾɾɾ/>

    View Slide

  10. RecyclerViewにAdapterをセットする
    // container͕RecyclerViewͰ͢
    adapter = RecyclerAdapter(list)
    // onDestroyͳΓͰdetach͢Δ
    snapHelper.attachToRecyclerView(container)
    container.setHasFixedSize(true)
    container.layoutManager = LinearLayoutManager(
    this,
    LinearLayoutManager.HORIZONTAL, // ਫฏํ޲ͳͷͰ…
    false)
    container.adapter = adapter

    View Slide

  11. レベル2
    アイテム間にスペースを

    つけるカルーセル

    View Slide

  12. Spaceを入れるItemDecorationをつけよう
    • getItemOffsetsをoverrideしたItemDecorationを用意する
    • 引数のoutRectのleft/rightを必要なspace分ずらす
    • 実装したItemDecorationをRecyclerViewにaddするだけ
    container.addItemDecoration(SpaceItemDecoration(SPACE_SIZE))

    View Slide

  13. ItemDecorationの中はこんな感じ
    override fun getItemOffsets(
    outRect: Rect,
    view: View,
    parent: RecyclerView,
    state: RecyclerView.State) {
    outRect.set(0, 0, space, 0) // ඞཁͳϚʔδϯΛηοτ
    // outRect.right = space ͱ͔Ͱ΋OKɻͨͩ͠஋͸pxͳͷͰdp -> px͢Δ
    }

    View Slide

  14. なんか左寄りなので真ん中にしたい
    override fun getItemOffsets(
    outRect: Rect,
    view: View,
    parent: RecyclerView,
    state: RecyclerView.State) {
    // ࠨӈۉ౳ʹϚʔδϯ͕౰ͨΔΑ͏ʹ͢Δ
    outRect.set(space / 2, 0, space / 2, 0)
    }

    View Slide

  15. 良さそう
    ちゃんと真ん中に寄ってる

    View Slide

  16. レベル3
    隣のアイテムが見えるカルーセル

    View Slide

  17. やりたいことは…
    • 触っていないときは隣のアイテムが見えている状態にしたい
    • 画面幅に対して左右60dpずつマージンをとる
    • itemが並ぶ間隔はマージンの半分である30dpにする
    2 3
    1
    60dp
    30dp

    View Slide

  18. 変えないといけないこと
    • 元々がwidthがmatch_parentなので、このままではいくらItemDecorationでOffsetをつけて
    も隣のViewは同じようには見えない
    • OffsetでItemのサイズが変わるので無理やり一つ見えてもほかのサイズが変わったり…
    • アイテム自体を固定値に…できたら良かった
    • 世の中の端末すべてが横幅360dpではない
    • 隣のアイテムが見える + アイテムが中央に来るは達成できるが…
    • 端末によっては中央にアイテムが来るように調整した結果

    余白が大きくスカスカに見える
    • Eg. Nexus 5X -> 411dp、Essential PH-1 -> 431dp…

    View Slide

  19. つまり…
    • match_parent時にしたときのitemのサイズを制限できればいい
    • ということは親が小さくなればいい
    • 親になるRecyclerView自体にPaddingを設定する

    View Slide

  20. 少し算数する
    • アイテム間のスペースが30dpの時、RecyclerViewのPaddingを60dpにして、かつ中央にア
    イテムが来るような値のとり方を考える
    • 前述の通り、アイテムの中央寄せの為に左右に同じスペースをとる
    • つまりアイテムの左右には15dpを与える
    • 最初のアイテムも左側には15dp持つことになるので、Paddingを安直に60dpにすると75dp
    になる。ここは60dpで抑えたい
    • よってRecyclerView取るべきPaddingは60-15=45dp
    2 3
    1
    60dp
    30dp

    View Slide

  21. 少し算数する(一般化)
    • アイテム間のスペースがx[dp]の時、RecyclerViewのPaddingを2x[dp]にして、かつ中央に
    アイテムが来るような値のとり方を考える
    • 前述の通り、アイテムの中央寄せの為に左右に同じスペースをとる
    • つまりアイテムの左右には0.5x[dp]を与える
    • 最初のアイテムも左側には0.5x[dp]持つことになるので、Paddingを安直に2x[dp]にすると
    2.5x[dp]になる。ここは2x[dp]で抑えたい
    • よってRecyclerViewが取るべきPaddingは2x-0.5x=1.5x[dp]
    • XML上に書いてもItemDecorationには反映されないのでこれをコードで定義する
    • SpaceItemDecorationを作る際に一緒にRecyclerViewを引数に渡してみる

    View Slide

  22. いい感じにマージンを設定する
    fun from(
    recyclerView: RecyclerView,
    itemSpaceDp: Int
    ): SpaceItemDecoration {
    val paddingPx = itemSpaceDp.toPx() // ͍͍ײ͡ʹdp -> pxม׵
    // RecyclerViewʹ༩͑Δpaddingʢ1.5x[dp]Λ༻ҙ͢Δʣ
    val edgePaddingPx = (paddingPx * 3 / 2).toInt()
    recyclerView.setPadding(edgePaddingPx, 0, edgePaddingPx, 0)
    return SpaceItemDecoration(paddingPx.toInt())
    }

    View Slide

  23. やったか…?
    隣が見えていない
    (´・ω・`)

    View Slide

  24. つまり…
    • match_parent時にしたときのitemのサイズを制限できればいい
    • ということは親が小さくなればいい
    • 親になるRecyclerView自体にPaddingを設定する
    • RecyclerView自体にPaddingをつけても隣は結局見えない

    View Slide

  25. つまり…
    • match_parent時にしたときのitemのサイズを制限できればいい
    • ということは親が小さくなればいい
    • 親になるRecyclerView自体にPaddingを設定する
    • RecyclerView自体にPaddingをつけても隣は結局見えない
    • clipToPaddingをfalseにすることで見えるようになる
    • デフォルトではPaddingによる余白の描画は切り取るようになっているがそれを表示する
    • paddingされたコンテンツの外側にあるものが見えるようになる

    View Slide

  26. いい感じにマージンを設定する
    fun from(
    recyclerView: RecyclerView,
    itemSpaceDp: Int
    ): SpaceItemDecoration {
    val paddingPx = itemSpaceDp.toPx() // ͍͍ײ͡ʹdp -> pxม׵
    // RecyclerViewʹ༩͑Δpaddingʢ1.5x[dp]Λ༻ҙ͢Δʣ
    val edgePaddingPx = (paddingPx * 3 / 2).toInt()
    recyclerView.setPadding(edgePaddingPx, 0, edgePaddingPx, 0)
    recyclerView.clipToPadding = false // ͜ΕͰྡ͕ݟ͑Δ
    return SpaceItemDecoration(paddingPx.toInt())
    }

    View Slide

  27. 良さそう
    ちゃんと隣も見えていて
    中央に寄ってる

    View Slide

  28. まとめ
    • RecyclerViewでいろんなカルーセルを作る話でした
    • 結局作りたかったのは「隣のitemがちらっと見えるカルーセル」
    • 今回は「画面幅に対するPaddingの半分の間隔でitemが並んでいる時」の話なので

    デザインで異なる場合はそれにあった算数が必要
    • EpoxyのCarouselItemを使うと setItemSpacingPx でアイテム間のスペースを

    設定できるのでItemDecorationを書かなくてもなんとかなるのかな
    • 未検証です

    View Slide

  29. ありがとうございました

    View Slide