概要
カッコイイアニメーションをアプリで実装したいですよね?
その時にAndroidアプリを作っている時にエンジニアもデザイナもマテリアルデザインのアニメーションを実現できるのかよく分かっていない事が多いと思います。
エンジニアとしても実装できるのか分からず、また、デザイナーからもデザインとして上がってこないため実装しようとしません。またデザイナとしても無茶なお願いはしたくないので、エンジニアにお願いするのが忍びないというところがあるのではないかと思いました。
そこで実装カタログを作って、デザイナがこれを作れないかエンジニアに言い、それをエンジニアが見て実装する感じに出来たらいいなと思い書いてみました。
カタログへのまとめ方
日本語のマテリアルデザインガイドライン のモーションに対しての実装方法を紹介します。(そこから引用させていただいています。)
ちなみに実装方法の方針としては、できるだけPlaidなどのGoogleのサンプルのやり方を取り入れて実装するという方法で行っています。
いくつかの実装がなかったり必要なさそうな部分は紹介していません。
またサードパーティのライブラリに依存せず、公式で、Canvasなどに手を入れたり大変な実装を行わない方向でまとめています。
# カタログ
動的な継続時間 | イージングカーブ | |
---|---|---|
画像 | ||
対応API Level※1 | 1 | 7(Support Library) |
実装難易度※2 | ★ | ★ |
画面境界内での運動 | 矩形型変形 | |
---|---|---|
画像 | ||
API Level | 21 | 11 |
実装難易度 | ★★ | ★ |
放射型変形 | 分割と結合 | |
---|---|---|
画像 | ||
API Level | 21 | ? |
実装難易度 | ★★★ | 実装方法不明 |
連続性 | 作成 | |
---|---|---|
画像 | ||
API Level | 21 | 7(Support Library) |
実装難易度 | ★★★ | ★ |
放射状の反応 | アイコン | イラスト | |
---|---|---|---|
画像 | |||
API Level | 21 | 11 (Support Library) | - |
実装難易度 | ★★ | ★★ | (作るモノによる) |
※1 サードパーティのライブラリに依存せず、公式で、Canvasなどに手を入れたり大変な実装を行わない場合のAPI Levelのものです。
APIレベルとAndroidのバージョン
7 = Android 2.1 Eclair MR1
11 = Android 3.0 Honeycomb
14 = Android 4.0 ICS
21 = Android 5.0 LOLLIPOP
※2 参考程度に。 ★3つでもだいたいの機能の実装よりは小さいはず、、です。。
継続時間とイージング
この章はアニメーションの継続時間とイージングについて書かれています。
基本的には認識できて、最速というところを目指していくと良いみたいです。
またイージング、動く速度の変化などは方法が提供されているので、それを利用しましょう。
動的な継続時間 一般的な継続時間
動的な継続時間、一般的な継続時間では、どのような時間でアニメーションさせるかが書かれています。
ガイドラインより
すべてのアニメーションの継続時間を同一にするのではなく、
移動距離、要素の速度、サーフェスの変化に合わせてアニメーションごとに継続時間を調整します。
モバイル
モバイル端末での移行は、次のような差異はありますが、一般に継続時間は300 ms を超えます。
* 大きく、複雑で、全画面の移行の場合は、継続時間を長めにする(375 ms を超えてもよい)
* 画面に入るオブジェクトの場合、継続時間は 225 ms を超える画面から出るオブジェクトの場合、継続時間は 195 ms を超える
400 ms を超える移行は遅すぎると思
われる可能性があります。
実装
アニメーションの時間を変えるために、setDurationなどで時間を調整します。
→ 175msから375msの間で動かすといいみたいです。 175ms=0dpの移動 375ms=500dp以上の移動みたいな感じで調整します。
ただこれは1オブジェクトが動く場合で、画面遷移などでいくつかのオブジェクが動いたりする場合は375msにしたりするみたいです。
自然なイージング カーブ
アニメーションの速度は一定ではなく、イージングカーブを利用して、変速させます。
標準カーブ、減速カーブ、加速カーブ、急カーブという種類があり、それぞれを場合によって使い分けて利用します。
標準カーブ
ガイドラインより
最も一般的なイージング カーブです。
画面上の位置と位置の間で、オブジェクトはすぐに加速し、ゆっくりと減速します。
これは、プロパティ変更の中でも、拡大、縮小するマテリアルに適用されます。
画面境界内の 2 地点間の要素の運動は、自然な凹型の弧を描きます。
画面上の運動はすべて、標準カーブを使用します。
実装
→利用場所: 画面内の運動
→AndroidではFastOutSlowInInterpolatorを使う これはSupport Libraryにもあるのでそれを利用できる
https://developer.android.com/reference/android/support/v4/view/animation/FastOutSlowInInterpolator.html
実装例:
commentVotes.animate()
.translationX(0f)
.alpha(1f)
.setDuration(200L)
.setInterpolator(new FastOutSlowInInterpolator());
ちなみにアニメーションにはGingerbread(2.3) 以前をサポートする必要がなければAniamtor関係のクラスを使うと良いようです。(これはViewPropertyAnimatorを使う例です。)
減速カーブ
ガイドラインより
オブジェクトは画面の外からフルスピードで画面に入り、
停止点に向かってゆっくりと減速します。
減速時に、オブジェクトのサイズ(最大 100%)または不透明度(最大100%)を大きくしていくことができます。
オブジェクトが不透明度 0% で画面内に入る場合は、
画面に入る時点の大きいサイズから若干小さくすることもできます。
実装
利用場所: 画面に入る運動
速度変更のためにAndroidではLinearOutSlowInInterpolatorを使う こちらもSupport Libraryあり
https://developer.android.com/reference/android/support/v4/view/animation/LinearOutSlowInInterpolator.html
標準カーブの実装例のFastOutSlowInInterpolatorをLinearOutSlowInInterpolatorに置き換えるのみです。
画面内に入る時に透明度同時にalpha()
を使えば透明度もアニメーションすることができます。
加速カーブ
ガイドラインより
オブジェクトはフルスピードで画面から出ます。画面から出るときは減速しません。
アニメーションの開始時に加速させ、サイズ(最小 0%)または不透明度(最小 0%)のいずれかを小さくしていくことができます。
オブジェクトが不透明度 0% で画面から出る場合は、サイズを若干大きくまたは小さくすることもできます。
実装
→利用場所: 画面から完全に出る運動
FastOutLinearInInterpolatorを使うことでできる
https://developer.android.com/reference/android/support/v4/view/animation/FastOutLinearInInterpolator.html
急カーブ
ガイドラインより
急カーブは、いつでも画面に戻ることができるオブジェクトに使用します。
オブジェクトは、画面上の開始点からすぐに加速させ、
その後、対照的なカーブで画面外の停止点に向けてすぐに減速させることができます。
正確なパスに沿って画面外の点に向かわないため、減速は標準カーブより高速になります。
オブジェクトはいつでもその点から戻ることができます。
実装
→利用場所: 画面から一時的に出る運動(ナビゲーションドロワーみたいなもの)
→実装なし ただPathInterpolatorCompatを使えば実装できそうです。(PathInterpolatorCompat.create(0.4, 0, 0.6, 1)
検証できていないです)
https://developer.android.com/reference/android/support/v4/view/animation/PathInterpolatorCompat.html
運動
画面境界内での運動
画面境界内でアニメーションさせるときの方法について記述されています。
ガイドラインより
画面境界内の 2 地点間の要素の運動は、自然な凹型の弧を描きます。画面上の運動はすべて、
標準カーブを使用します。
実装
→丸くアニメーションさせる弧を用いた運動では、丸くアニメーションさせます(後述)
画面内の運動なので、標準カーブ(FastOutSlowInInterpolator)が利用されます。
ガイドラインより
現実の世界で重力に逆らって上昇するには、力が必要です。
画面上でも要素を上方向に移動するには、減速しながら上に向かうという運動を通して
加速中の力を表現する必要があります。
実装
TransitionアニメーションとPathMotionを使って実装します。
Support LibraryにもTransitionクラスはあるのですが、現状ではPathMotionに対応していないようなので、利用できません。
テーマにandroid:windowSharedElementEnterTransition
を設定する
<style name="Plaid.Translucent.DesignerNewsStory">
...
<item name="android:windowSharedElementEnterTransition">@transition/designer_news_story_shared_enter</item>
<item name="android:windowSharedElementReturnTransition">@transition/designer_news_story_shared_return</item>
そのtransitionとして以下のようにセットする
<transitionSet
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="350"
android:transitionOrdering="together">
<transition
class="android.transition.ChangeBounds"
android:interpolator="@android:interpolator/fast_out_slow_in">
<targets>
<target android:targetId="@id/shot" />
</targets>
</transition>
<pathMotion class="com.github.takahirom.plaidanimation.GravityArcMotion" />
</transitionSet>
ダメな例 | 良い例 |
---|---|
ArcMotionクラス | GravityArcMotionクラス |
pathMotionとして指定するものは標準のフレームワークのクラスであるArcMotionクラスが近いのですが、普通にやると不適切とされている動きをします。 | Googleのサンプル実装のPlaidにカスタマイズした実装があり、こちらはマテリアルデザインに沿った形でPathMotionクラスをカスタマイズしています。GravityArcMotionクラス これを使うことで以下のようにガイドラインに沿ったアニメーションが可能です。
実装例:
https://github.com/takahirom/PlaidAnimation/commit/8ad520bcd9ffa7f5bad8d9bd4727789aa1d46675
マテリアルの変形
マテリアルの形を変える時のガイドラインです。
矩形型変形
矩形型 つまり長方形の変形です。アニメーションを上下と左右でずらすことによってこれを実現します。
ガイドラインより
実装
→ CardView + アニメーションの幾つかの方法で実装可能
例:
final CardView cardView = (CardView) findViewById(R.id.card);
cardView.setOnClickListener(new View.OnClickListener() {
boolean isSmall = true;
@Override
public void onClick(View view) {
if (isSmall) {
// 大きくする
ViewCompat
.animate(view)
.scaleX(3.0f)
.setDuration(275)
.setStartDelay(0)
.setInterpolator(new FastOutSlowInInterpolator())
.start();
ViewCompat
.animate(view)
.scaleY(3.0f)
.setDuration(350)
.setStartDelay(25) // 開始をずらす
.setInterpolator(new FastOutSlowInInterpolator())
.start();
} else {
// 小さくする
ViewCompat
.animate(view)
.scaleX(1.0f)
.setDuration(325)
.setStartDelay(50)
.setInterpolator(new FastOutSlowInInterpolator())
.start();
ViewCompat
.animate(view)
.scaleY(1.0f)
.setStartDelay(0)
.setDuration(325)
.setInterpolator(new FastOutSlowInInterpolator())
.start();
}
isSmall = !isSmall;
}
});
放射型変形
丸く広がるアニメーションです。FloatingActionButton (FAB)を変形させて、カードにしたりする場合に利用されます。
ガイドラインより
https://material.io/guidelines/motion/transforming-material.html#transforming-material-radial-transformation
実装
SharedElementTransitionを利用して以下のようにコーディングします。
カスタムTransition
カスタムTransitionとして指定しているFabTransformクラスは以下のように実装してあげることであのようなアニメーションができます。
注目するべきはTransitionクラスで実装する必要があるcreateAnimatorメソッドです。開始時の位置が取得できるTransitionValues startValues
と終了時の位置とViewが取得できるTransitionValues endValues
が引数として渡ってくるので、自分でアニメーションさせるAnimatorを作成します。
@Override
public Animator createAnimator(final ViewGroup sceneRoot,
final TransitionValues startValues,
final TransitionValues endValues) {
// 開始場所の取得
final Rect startBounds = (Rect) startValues.values.get(PROP_BOUNDS);
// 終了場所の取得
final Rect endBounds = (Rect) endValues.values.get(PROP_BOUNDS);
// アニメーションするViewの取得
final View view = endValues.view;
まず円形に広がるAnimatorであるCircularRevealを作成します。
ViewAnimationUtils.createCircularRevealにより、始まりのx、yと、終わりの大きさを指定して作成します。
// Circular clip from/to the FAB size
final Animator circularReveal;
if (fromFab) {
circularReveal = ViewAnimationUtils.createCircularReveal(view,
view.getWidth() / 2,
view.getHeight() / 2,
startBounds.width() / 2,
(float) Math.hypot(endBounds.width() / 2, endBounds.height() / 2));
circularReveal.setInterpolator(
new FastOutLinearInInterpolator());
最終的に円形で広がるcircularReveal
と位置を変更するtranslate
と色を変更するcolorFade
と元のiconをフェードアウトするiconFade
を同時に動かしています。
final AnimatorSet transition = new AnimatorSet();
transition.playTogether(circularReveal, translate, colorFade, iconFade);
transition.playTogether(fadeContents);
分割と結合
マテリアルデザインの動画でも出てくるマテリアルの結合と分割です。
ガイドラインより
https://material.io/guidelines/motion/transforming-material.html#transforming-material-joining-dividing
結合
マテリアルを他のマテリアルと結合させたり、複数のパーツに分割したりできます。
マテリアルの 2 つのパーツが互いに接近しながら、
縁同士がぶつかり、マージンが重なって、運動が完了します。
分割
マテリアルを複数のパーツに分割する場合は、動き始めた時点でパーツが分離し始めます。
シャドウ
切り離されたマテリアルのシャドウは、兄弟要素の上には表示されません。
実装
正直わからないです。
どなたか分かる方いたら教えてください
ただのCardであれば1つのCardViewを4つのCardViewにして、ViewPropertyAnimatiorなどで離すのみですが、、コンテンツもあると実装苦しそうです
RecyclerViewなどでもできそうですがどうでしょうか。実装例をご存じの方はお教えください。
コレオグラフィ
コレオグラフィの意味よくわからなかったのですが、マテリアルデザインにおける “Choreography“(振り付け)を知ろうという記事があり「ユーザーを適切な場所へ誘導するための手法」ということだそうです。
連続性
一つの同じオブジェクトを保持したまま遷移後も利用する連続性です。
ガイドライン
コンテンツのすべての要素が共有される場合サーフェスを展開するとき、
遷移中にかなりの数の要素を表示し続ける必要があります。
複雑な遷移では、1 つの要素を常に見えるようにしてください(下のアニメーションをご覧ください)。
実装
こちらについてはこれまで紹介しているSharedElementTransitionで実装可能となります。
放射型変形と同じように細かい調整のためにカスタムTransitionを使う必要があると思われます。
作成
新しいマテリアルを作成するときに利用するものです。メニューボタンなどで利用することがあると思います。
ガイドライン
https://material.io/guidelines/motion/choreography.html#choreography-creation
実装
android.support.v7.widget.ListPopupWindowクラスを使うことで、近いものはかんたんに実現できます。
一応こちらでもユーザーの視線を誘導することはできているので目的は果たせそうです。
ガイドラインのようにリストを上に出すために、ちょっとしたハックをしていますが、基本的に必要ないはずです。
final ListPopupWindow listPopupWindow = new ListPopupWindow(MainActivity.this);
final ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this, R.layout.support_simple_spinner_dropdown_item);
adapter.add("test");
adapter.add("test2");
listPopupWindow.setWidth(dip2px(200));
listPopupWindow.setAdapter(adapter);
listPopupWindow.setAnchorView(view);
// show top of anchor view
// FIXME: ugly hack
int[] attribute = new int[] { R.attr.dropdownListPreferredItemHeight };
TypedArray array = obtainStyledAttributes(0, attribute);
int rowHeight = array.getDimensionPixelSize(0, -1);
listPopupWindow.setVerticalOffset(-view.getHeight() - rowHeight * adapter.getCount());
listPopupWindow.show();
ガイドラインより
実装
nissiyさんが書いている『OSSから学ぶActivity起動時のカッコいいアニメーション』の『ふわっと表示される各写真』にあるようにRecyclerViewのAdapterのonBindViewHolderでやるのが良さそうです。
http://qiita.com/nissiy/items/9b978ed1925f3605a25c#%E3%81%B5%E3%82%8F%E3%81%A3%E3%81%A8%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%82%8B%E5%90%84%E5%86%99%E7%9C%9F
斜めのときのやり方は行数と列数足し算して、setStartDelayを設定するような感じでできそうです。
放射状の反応
タッチした場所から丸く広がるアニメーションです。
ガイドラインより
実装
放射型変形と同じようにViewAnimationUtils.createCircularReveal
を使い、タップされた場所から広がるように引数に座標を渡せばできそうです(未実装)
クリエイティブなカスタマイズ
アイコンをアニメーションさせたり、何もない状態のときに楽しい画像を出したりというところです。
アイコン
アイコンのアニメーションは2つの機能をトグルするような使い方が主な使い方のようです。
ガイドラインより
1 つのアイコンで 2 つの機能を果たすことができます。
たとえば、メニューアイコンが再生コントロールにスムーズに移行し、
再びメニューに戻ることができれば、
ユーザーにボタンの機能を伝えると同時に、
操作に驚きの要素を加味できます。
実装
回転と移動程度であればAnimatedVectorDrawableCompatにより実現可能です。
AnimatedVectorDrawableCompatの作成方法についてはAndroidIconAnimatorというツールを使うことにより割と簡単に作ることができます。
ちなみにメニューが戻るボタンになったりするのは、サンプルにあるので、それを参考に作ってみると良いと思われます。
https://speakerdeck.com/takahirom/support-vector-drawable
https://romannurik.github.io/AndroidIconAnimator/
アニメーションアイコンが作れるAndroid Icon Animatorを触ってみる
http://qiita.com/takahirom/items/3deee8b73e0a2bc1a50b
Android Icon Animatorを活用してアニメーションリソース軽量化
http://tech.vasily.jp/entry/android_icon_animator_tutorial
イラスト
何もない状態のときにアニメーションさせたりというところです。
ガイドラインより
画像やイラストのわずかなアニ
メーションで、ユーザー エク
スペリエンスに楽しさを加える
ことができます。
実装
このアニメーションはAnimatedVectorDrawableでもいいですし、Glideなどを利用して、gif画像を利用してもいいと思います。
下位互換性はどうするか?
API Level 21未満のユーザーを現状では切り捨てることはだいぶ難しいです。
Support Libraryは下位互換性を保持してくれるライブラリですが、現状はサポートしきれていない部分があります。
そこで2つのアプローチがあります。
アプローチ1 下位バージョンではアニメーションを諦める
今からスマホにアプリ入れて来ている新規のユーザーは結構5.0以上が多いです。
公式のDashboardsより、新規だけでなく60%以上のユーザーがAndroid 5.0以上を使っています。
なので、その新規のユーザーにフォーカスして諦める。というのも良いと思います。
そして、Support Libraryの対応を待つというアプローチです。
コード例
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Android 5.0以上のユーザーだけここでアニメーション
}
アプローチ2 頑張って実装する
外部のライブラリなどを用いたり、自分で頑張って行います。
すべてのユーザーにアニメーションの体験が提供できるので嬉しいです。
ただ公式の方法とは異なるので、微妙に動きが違ったり、サポートを受けにくくなったりなどデメリットがあるので、考えて行う必要があります。
終わりに
マテリアルデザインは結構浸透していますが、モーションは中々実践できていない部分ではないかと思います。下位バージョンを除いてモーションは実装する方法は一通りだいたい用意されているようです。
ただ、マテリアルデザインのモーションはこれで一通りみたのですが、Patternsなどに基礎を応用したさまざまなアニメーションがあるのですが、そこについては説明できていません。しかし、だいたいは何かしらやり方はあるはずなので、調べてみてください。
ガイドラインにある重要性、目的を意識しつつ、採用していければ、ユーザーが迷わなかったり、待ち時間を意識せずに利用できたりなどをしていけるようになるはずです。
モダンでイケイケなアプリを作っていきましょう!
マテリアルモーションのモーションの重要性より