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

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

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

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

0f50b010cc99988fba8a73008b21f353?s=128

Yoshihiro WADA

December 13, 2018
Tweet

Transcript

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

  2. 自己紹介 • Yoshihiro Wada a.k.a. @e10dokup • 大体右のアイコンで息をしています • CyberAgant

    Inc. / Ameba • Amebaブログやってます • We’re Hiring • カメラ沼 • 財布の透過率が高いのが最近の悩み
  3. 今日のコンテンツ • カルーセルを作る • ループするしないとかするとかいろいろあるが… • 今回は画面横方向に面が並んだものと定義します • 正直EpoxyにCarouselItemってあるやん…? 2

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

    本音:Groupieで雑にAdapterを作って管理したかった 2 1 3
  5. やっていきましょう

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

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

  8. SnapHelper • RecyclerView.onFlingListenerを継承したAbstract class • フリングしたときに子Viewにスナップさせる • 継承クラスは2つ • LinearSnapHelper

    • snap対象のViewがRecyclerViewの中心に来るようにスナップ • PagerSnapHelper • ViewPagerっぽい挙動のスナップ • SnapHelper#attachToRecyclerView(recyclerView)でアタッチ • nullを入れるとdetach。多重attachしないように気をつける
  9. レイアウトを用意して <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="150dp" android:background="#EEEEEE"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" ɾɾɾ/>

    </android.support.constraint.ConstraintLayout>
  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
  11. レベル2 アイテム間にスペースを
 つけるカルーセル

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

  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͢Δ }
  14. なんか左寄りなので真ん中にしたい override fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView,

    state: RecyclerView.State) { // ࠨӈۉ౳ʹϚʔδϯ͕౰ͨΔΑ͏ʹ͢Δ outRect.set(space / 2, 0, space / 2, 0) }
  15. 良さそう ちゃんと真ん中に寄ってる

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

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

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

    • 隣のアイテムが見える + アイテムが中央に来るは達成できるが… • 端末によっては中央にアイテムが来るように調整した結果
 余白が大きくスカスカに見える • Eg. Nexus 5X -> 411dp、Essential PH-1 -> 431dp…
  19. つまり… • match_parent時にしたときのitemのサイズを制限できればいい • ということは親が小さくなればいい • 親になるRecyclerView自体にPaddingを設定する

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

    になる。ここは60dpで抑えたい • よってRecyclerView取るべきPaddingは60-15=45dp 2 3 1 60dp 30dp
  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を引数に渡してみる
  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()) }
  23. やったか…? 隣が見えていない (´・ω・`)

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

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

    clipToPaddingをfalseにすることで見えるようになる • デフォルトではPaddingによる余白の描画は切り取るようになっているがそれを表示する • paddingされたコンテンツの外側にあるものが見えるようになる
  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()) }
  27. 良さそう ちゃんと隣も見えていて 中央に寄ってる

  28. まとめ • RecyclerViewでいろんなカルーセルを作る話でした • 結局作りたかったのは「隣のitemがちらっと見えるカルーセル」 • 今回は「画面幅に対するPaddingの半分の間隔でitemが並んでいる時」の話なので
 デザインで異なる場合はそれにあった算数が必要 • EpoxyのCarouselItemを使うと

    setItemSpacingPx でアイテム間のスペースを
 設定できるのでItemDecorationを書かなくてもなんとかなるのかな • 未検証です
  29. ありがとうございました