テーマ:

こんにちは。
AWAでAndroidエンジニアをしている新家(ニイノミ)です。

最近インタラクションって言葉、よく聞きますよね。
インタラクションの役割や意味合い等はここでは割愛しますが、「インタラクションをつくる」といった場合、コードを書くエンジニアとしては「動きをつくる」という意味合いが大きいかと思います。

この「動き」について、今まではゲームやFlashを用いたスペシャルサイトなど、世界観の構築やブランディングが必要なサービスで用いられることは多かったものの、SNSなどのスマホのコミュニティ系サービスで取り入れられることはあまり多くありませんでした。
しかし、ユーザーの操作感や体験を重視するようになってきた昨今、コミュニティ系サービスの開発においても「動きをつくる」要件は増えてきました。
実際、僕が開発に携わっているAWAもユーザー体験を重視しており、その一つとしてインタラクションにはこだわって開発をしてきました。

プログラミング言語に備わっているAPIを用いれば簡単に動きを作れることもありますが、技術の移り変わりが激しい昨今、枯れない技術として本質的な部分を理解しておくことは非常に重要です。

そこで今回は、簡単な知識を用いて動きをつくるための方法をいくつか紹介します。
ソースコードは、動きの基本的な部分の紹介にはProcessingを、実際のサービスでの使い方などの紹介にはAndroidを用います。

正規化

正規化とは、値を一定のルールに基づいて変形し、利用しやすくすることです。
対象となる物体を移動させたり、大きさを変える際にこの正規化が結構役立ちます。
ここでは、よく使う3つの正規化を紹介します。

normalize

ある範囲の値を0.0 ~ 1.0の間の値に変換します。
計算式は単純で、(初期値 - 最小値) / (最大値 - 最小値)となります。
processingではnorm()という関数で用意されてます。

lerp

線形補間とも言います。
normalizeとは逆に0.0 ~ 1.0の範囲の値を別の範囲の値に変換します。
計算式は、最小値 + (最大値 - 最小値) * 初期値(0.0 ~ 1.0)
processingではlerp()

map

ある範囲の値を別の範囲の値に変換します。
normalizeとlerpを組み合わせてできています。
lerp(最小値B, 最大値B, norm(初期値, 最小値A, 最大値B))
processingではmap()

この3つの中でもmapは特に便利で、実際にAWAのAndroidアプリでもスクロールに応じたアイテムの位置を自身のサイズに変換するなどの処理でこの計算式を用いています。

Android Java

1.for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
2. View targetView = mRecyclerView.getChildAt(i);
3. float scaleRate, centerY = mWinHeight / 2;
4.
5. if (targetView.getTop() > distance) {
6. // 拡大
7. scaleRate = map(targetView.getTop(), mWinHeight, centerY, 0.9f, 1f);
8. } else {
9. // 縮小
10. scaleRate = map(targetView.getTop(), centerY, 0, 1f, 0.9f);
11. }
12.
13. targetView.setScaleX(scaleRate);
14. targetView.setScaleY(scaleRate);
15.}



画面の中のアイテムについて、

位置 -> サイズ

に変換しています。

このように、あるプロパティの値を別のプロパティに適用するなどの場合に正規化は役立ちます。

速度と加速度

毎フレームごとに位置を更新することで物体を移動させるアニメーションを表現できます。
この移動について、速度と加速度の観点から考えてみましょう。


上は一定の速度で移動しているのに対して、下は徐々に速くなっているのがわかるかと思います。
これは、上は毎フレームごとに速度を位置に加算して等速運動を表現しており、下は位置に加算する速度に加速度を加えているからです。

つまり、加速度は速度に作用し、速度は位置に作用するという関係が言えます。

1.// 等速運動
2.位置 += 速度
3.
4.// 加速運動
5.速度 += 加速度
6.位置 += 速度

これは動きをシミュレートする際の基本的な考え方になります。

イージング

イージングは加速や減速など、動きに緩急をつけるための仕組みです。
イージングを変えることで、加速や減速などのアニメーション表現が可能になり、より現実世界の動きに近づけることができます。


UIを構築するための多くのプログラミング言語では様々なイージングのクラスが用意されており、普段はそれらを利用すれば良いのですが、ここではそのイージングがどのように動きに影響しているかを見ていきたいと思います。

イージングの値は数式によって求められ、この数式によってできるグラフをイージングカーブと呼びます。

下のグラフはy = x^4のイージングカーブです。


x 軸を時間、y軸をイージング値と考えると、最初はほとんど値が変わらず、0.6秒を超えたあたりから急激に割合が大きくなっていることがわかります。
つまり、これは加速のイージングカーブです。

移動する物体にこのイージングカーブを適用すると以下のようになります。


徐々に加速していますね。

イージング値を使って現在位置を求める式は次のようになります。

現在位置 = 初期位置 + (目標位置 - 初期位置) * イージング値(0.0 ~ 1.0)

これを使って、ボールが下から上に500msかけて移動するプログラムは以下のようになります。

Processing

1.static final float FPS = 60;
2.float elapsedTime = 0;
3.float totalDuration = 500;
4.
5.void draw() {
6. if (elapsedTime < totalDuration) {
7.   // 1フレームあたりのミリ秒を加算
8. elapsedTime += (1000 / FPS);
9. } else {
10. elapsedTime = totalDuration;
11. }
12. // y = x^4
13. float rate = pow(getNormalizedElapsedTime(), 4);
14. x = width;
15. // Proccesingでは左上が原点になるので、これを加味して位置を計算
16. y = height - height * rate;
17.
18. // 描画
19. ellipse(x, y, 10, 10);
20.}
21.
22.// 経過時間を0.0 ~ 1.0の範囲に変換
23.float getNormalizedElapsedTime() {
24. return norm(elapsedTime, 0, totalDuration);
25.}

※細かい部分は省略してあります。

このように、式によって得られたイージング値(0.0 ~ 1.0)を物体のy座標に適用することで加速を表現することができます。

他にも式を変えることで動きの緩急を変化させられます。

y = 1 - (1 - x)^4



y = x^0.5

ここで用いたのはシンプルな指数関数だけですが、もっと複雑なイージングの計算式はネット上で色々見つけられます。
例えば、有名なRobert Pennerのイージンング関数なんかはプログラムとしてもすぐに使えそうです。

ベジェ曲線からイージングカーブをデザインする

先ほどは計算式からイージングカーブを作りましたが、複雑な動きを簡単に表現するにはベジェ曲線を用いるのも有効です。
ベジェ曲線とは、N 個の制御点から得られる N - 1 次曲線のことです。

多くの言語ではベジェ曲線からイージングカーブをつくるAPIが用意されているので、これらを用いれば簡単に動きをアレンジすることができるでしょう。

AndroidならPathInterpolatorを用いることができます。
※API level 21

Android Java

1.ObjectAnimator animator = ObjectAnimator.ofFloat(targetView, "translationX", 0, winWidth);
2.animator.setDuration(500);
3.animator.setInterpolator(new PathInterpolator(0.71f, -0.88f, 0.32f, 1.68f));
4.animator.start();

ベジェ曲線の制御点の計算はわかりにくいですが、ベジェ曲線を作成できるサイトがあるので、制御点を移動させながら自分が目指す動きを作っていくと良いと思います。

より詳しいイージングカーブのデザイン方法についてはこちらの記事がわかりやすいです。

グラフソフトを使ってイージングカーブをデザインする

複雑な動きを作るには、複数の数式を組み合わせてイージングカーブを作り、経過時間ごとに適用する数式を変えるような処理にしたほうが良い場合もあります。
Macに標準搭載されているGrapherなどのグラフ描画ソフトを用いれば簡単に数式からグラフを作成することができます。


このグラフをプログラムでイージング値に変える場合、AndroidではInterpolatorを実装した独自クラスを作ることで実現できます。

Android Java

1.public class EaseInOutInterpolator implements Interpolator {
2. @Override
3. public float getInterpolation(float t) {
4. if (t < 0.4f) {
5. return (float)(8 * Math.pow(t, 2));
6.
7. } else if (t < 0.68f) {
8. return (float)(Math.pow(5 * t - 2.8f, 2) + 0.6f);
9.
10. } else if (t < 0.87f) {
11. return (float)(Math.pow(5 * t - 3.9f, 2) + 0.8f);
12.
13. } else {
14. return (float)(Math.pow(5 * t - 4.69f, 2) + 0.9f);
15. }
16. }
17.}

GrapherとInterpolatorを使ったAndroidの実装についてはこちらの記事が参考になるかと思います。

まとめ

駆け足になりましたが、インタラクションを支える技術の基本的な部分からいくつが抜粋して紹介しました。
今回紹介した以外にもベクトルやバネなどの重要な概念が存在するので、調べてみると良いかもしれません。

ProcessingとAndroidごちゃまぜで紹介したのでよくわからなかった人もいるかもしれませんが、基本的な動きを学習するにはProcessingはとても良い言語です。
そして、その基礎を理解した上でフレームワークのAPIを用いることで、エンジニアの書くコードと出来上がるインタラクションが共に良いものになるかと思います。

こういった本質的な動きの作り方に関する情報はまだあまり多くない印象なので、もっとたくさん出てくるといいですね。

最後までお付き合いいただきありがとうございました。

いいね!(15)  |  リブログ(0)