dely engineering blog

レシピ動画サービス「kurashiru」を運営するdelyのテックブログ

Androidにいい感じの動きをさせていく話(ViewPagerとその他Viewとの連動)

こんにちは。delyでAndroidのエンジニアをしているkenzoです。
この記事はdely Advent Calendar 2018の6日目の記事です。

Qiita: https://qiita.com/advent-calendar/2018/dely Adventar: https://adventar.org/calendars/3535

 

昨日は検索エンジニアのsakuraが「クラシルの検索をよくするために1年間取り組んだこと」を書きました。
普段検索に携わる方はもちろん、それ以外の方にとっても面白い内容となっていますので、ぜひご覧ください。

はじめに

弊社のプロダクト開発はデザインフェーズと実装フェーズの2つのフェーズに分かれています。(詳しくはこちら↓)

speakerdeck.com

後半の実装フェーズでは、前半のデザインフェーズで出来上がったデザインを元に画面・機能を作成していくことになります。
デザインフェーズでは、かなりいい感じに動くプロトタイプを作成したり、それを用いてユーザーテストを行ったりと、より良いものをユーザーに提供できるように弊社のデザイナーやデザインエンジニアが頑張ってくれています。

で、下のようなデザインが出来上がっているわけです。(これはデザインの指示ではなくて実際に実装した画面ですが)

f:id:kenzo_aiue:20181203183239g:plain

え、めちゃ動く。。。 (はじめてデザインを見たときのきもち)

初めて見ると一瞬たじろぐかもしれませんが、動きも含めてユーザーに提供する価値なので、難しそうだからといって実装しないわけにはいきません。

今回はこのようないい感じに動くデザインを実装に落とし込んでいく際にやったことの一例を紹介します。

ViewPagerとその他Viewとの連動

まずはこちらをご覧ください。

f:id:kenzo_aiue:20181204135415g:plain

このようにViewPagerを用いたアプリの場合、現在開いているページによってViewPager外に表示される内容が変わることがあると思います。(画面下部や右上の★)

もちろんこれでOKな場合も多いとは思いますが、もう少し細かい動きにこだわったUIを作成したい場合もあるかと思います。

たとえばこんな感じです。

f:id:kenzo_aiue:20181204141554g:plain

今回はこれの実装方法をご紹介します。

用いるのはOnPageChangeListeneronPageScrolledです。

val viewPager: ViewPager = findViewById<ViewPager>(R.id.view_pager)
viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
    
    override fun onPageScrollStateChanged(state: Int) {
    }

    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
    }

    override fun onPageSelected(position: Int) {
    }
})

onPageScrolledViewPagerのスクロール中に呼び出され、その際に引数として下記の値が渡されます。 (positionOffsetPixelsは今回は使いません)

  • position: 表示されている最初のページのindex
  • positionOffset: positionで指し示されるページからのオフセットの割合を0以上1未満の値で
  • positionOffsetPixels: position`で指し示されるページからのオフセットのpixel値

つまり、どのタイミングでどんな値が返るかというと、、

position: 0 positionOffset: 0.0

f:id:kenzo_aiue:20181204145013p:plain

position: 0 positionOffset: 0.31944445

f:id:kenzo_aiue:20181204145041p:plain

position: 0 positionOffset: 0.6259259

f:id:kenzo_aiue:20181204145142p:plain

position: 1 positionOffset: 0.0

f:id:kenzo_aiue:20181204145216p:plain

という感じです。

以上のような値がスクロールしている間に何度も呼ばれるonPageScrolledで渡されます。

この値を使ってその他Viewのtranslationやalpha、scaleをセットします。
上のサンプルの「いいいいい」のViewの例だと、

  • positionが0の時はpositionOffsetの増加に従って表示位置が上がる
  • positionが1の時はpositionOffsetの増加に従って表示位置が下がる
  • positionが2(上記以外)の時は常に表示位置が下のまま

なので、下記のようにtransilationYをセットします。

override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
    view.translationY = when (position) {
        0 -> view.height - view.height * positionOffset
        1 -> view.height * positionOffset
        else -> view.height.toFloat()
    }
}
// viewは「いいいいい」のviewです

このような動きになります。

f:id:kenzo_aiue:20181204141929g:plain

もう1つ、上のサンプルの「★」のViewの例だと、

  • positionが0の時(下記以外)は不透明度が0のまま
  • positionが1の時はpositionOffsetの増加に従って不透明度が上がる(だんだん見えるようになる)
  • positionが2の時は不透明度が1のまま(ページが増えるようならpositionOffsetの増加に従って不透明度を下げる)

なので、下記のようにalphaをセットします。

override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
    star.alpha = when (position) {
        1 -> positionOffset
        2 -> 1f // 今回はpositionの最大が2なのでこれでよいですが、ページが増える場合は1f - positionOffsetとします
        else -> 0f
    }
}
// starは「★」のviewです

このような動きになります。

f:id:kenzo_aiue:20181204133352g:plain

これで、ViewPagerをスクロールさせた時に他のViewと連動させることができました。

おわりに

Androidでも他のフロントエンドでも、動きのある画面を作るのはとっつきにくかったり難しかったりと、実装に若干のハードルはあると思います。
ですが、やっぱり自身がユーザーとしてサービスに触れる際に、いい感じに動いてくれると気持ちよかったり、わかりやすかったりと、良い体験ができていると感じます。
使ってくれる人がより良い体験をできるように、ものづくりをする人間としてはそういうとこ頑張らないとなーと思うところです。

 

ちなみに上でデザインの例としてあげた「クラシルかんたん献立機能」が今回リリースされました!
使ってみてもらえるととても嬉しいです。
アプリはこちら: Android iOS

 

明日はiOSエンジニアの堀口の「iOS版クラシルの開発からリリースまでの流れ」です。お楽しみに!