Androidで”動くスライドショー”を実装する〜Ken Burns Effect〜

こんにちは!PairsでAndroid開発をしている後藤です。

最近はGoの記事を中心に書いていましたが、ようやくAndroidエンジニアっぽさが出てきて安心しています。
今回は、”Ken Burns Effect”と呼ばれるアニメーションをどうAndroidで実装したか、についてお話ししたいと思います。

Ken Burns Effectについて

Ken Burnsという監督が使用していた撮影技法からこの名前が付けられました。Apple の iMovie に搭載されていることで有名です。弊社の「Couples」にあるスライドショー機能にもこのエフェクトが使用されています。

Animations

Ken Burns EffectにはTILT・PAN・ZOOMといった技法が使われます。これらのアニメーションを上手く組み合わせることでKen Burns Effectが出来上がります。

以下にコードが置いてあるので、すぐにコードを見たいという方はどうぞ。
https://github.com/gotokatsuya/KenBurnsView

TILT/PAN

上下/左右に動くアニメーションです。コードでは以下にようになります。

view.setTranslationX(fromTranslationX);
view.setTranslationY(fromTranslationY);
ViewPropertyAnimator propertyAnimator = view.animate().
translationX(toTranslationX).
translationY(toTranslationY).
setDuration(duration);
propertyAnimator.start();

ZOOM

拡大・縮小するアニメーションです。コードでは以下にようになります。

view.setScaleX(fromScale);
view.setScaleY(fromScale);
ViewPropertyAnimator propertyAnimator = view.animate().
scaleX(toScale).
scaleY(toScale).
setDuration(duration);
propertyAnimator.start();

始点と終点

KenBurndEffectを開始/終了するタイミングを毎回自動で決める必要があります。

上で書いたコードの

fromScale, fromTranslationX, fromTranslationY
toScale, toTranslationX, toTranslationY

を計算します。

拡大・縮小するための倍率を決める

最大倍率を決め、その間の値をランダムに取得します。

private float getScale() {
return 1.0f + Random.nextFloat() * (MAX - 1.0f);
}

上下左右に動かすための点を決める

Viewの長さと求めた倍率を考慮して、移動アニメーションのための始点と終点を決める必要があります。
考慮せずに終点が非常に大きな値になったりすると、非常に見栄えの良くないエフェクトになってしまうためです。

// value にはViewの最大長が入ります。
// ratio には拡大・縮小の倍率が入ります。
private float getTranslation(int value, float ratio) {
return value * (ratio - 1.0f) * (Random.nextFloat() - 0.5f);
}

例として、拡大しながら動いていくViewを想像しましょう。始点の倍率は 1.2, 終点の倍率は 1.5、Viewは縦横が400、Random.nextFloat()は 0.8 に固定して、getTranslationメソッドに当てはめて実際に計算してみました。

- 始点
startX = 400 * (1.2f - 1.0f) * (0.8f - 0.5f)
(startX, startY) = (6, 6)
- 終点
endX = 400 * (1.5f - 1.0f) * (0.8f - 0.5f)
(endX, endY) = (60, 60)
例なので x と y の値が同じになるようにしていますが、通常はランダムな値です。
この場合は (6, 6) ~ (60, 60) に移動しつつ 1.2 ~ 1.5倍まで拡大するアニメーションになります。
Ken Burns Effect
以上を組み合わせると下のようなコードになります。これでViewが上下左右に動きながら拡大・縮小するというKen Burnsのアニメーションが実現できます。
float fromScale = getScale();
float toScale = getScale();
float fromTranslationX = getTranslation(view.getWidth(), fromScale);
float fromTranslationY = getTranslation(view.getHeight(), fromScale);
float toTranslationX = getTranslation(view.getWidth(), toScale);
float toTranslationY = getTranslation(view.getHeight(), toScale);
view.setScaleX(fromScale);
view.setScaleY(fromScale);
view.setTranslationX(fromTranslationX);
view.setTranslationY(fromTranslationY);
ViewPropertyAnimator propertyAnimator = view.animate().
translationX(toTranslationX).
translationY(toTranslationY).
scaleX(toScale).
scaleY(toScale).
setDuration(duration);
propertyAnimator.start();
スライドショー
更にKen Burns Effectを使用してスライドショーの実装もしてみます。
要件としては以下です。
  • Effectをかけながら自動で画像が切り替わる。
  • スワイプして画像を切り替えられる。
  • 最後の画像まで切り替わったら最初の画像にまた戻る(ループし続ける)。
自動で画像を切り替える
3つのImageViewをKenBurnsViewの上に用意して、それを交互に入れ替える仕組みにします。
常に表示されているのは「2」のImageViewです。「1」と「3」はAlpha値を弄って透明化し、「1」には前の画像を、「3」には次の画像を読み込ませます。
画像の読み込みにはglideを使用しています。S3などの画像URLはもちろんですが、Resourceの読み込みにも対応しています。
スワイプして画像を切り替える
楽をしたかったのでスワイプの判定はViewPagerに任せています(もちろんスワイプ判定を自前で実装しても良いです)。KenBurnsViewの上にViewPagerを置き、ViewPagerのListenerが反応したらKenBurnsView上のImageViewを切り替えるイメージです。
最後の画像まで切り替わったら最初の画像にまた戻る
ViewPagerを使ってしまったせいで、これが非常に面倒でした。LoopできるViewPagerを用意して解決しました。ViewPagerにセットしたAdapterのgetCountメソッドに大数を返すようにして、擬似的にLoopしているように見せる手法を使っています。
最後に
以上でKen Burns Effect を使用したViewとスライドショーの作成についての説明は終わりです。空腹の皆様、お騒がせしました。紳士的なPRをお待ちしております。
Like what you read? Give eureka_developers a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.