ついつい押したくなる気持ちいい動きをするボタンを作る


# はじめに
私が今まで触った中で一番気持ちよかったボタンは、Moves（下記リンク）の中で使われている円形のボタンなのですが、これと似たような動きをするボタンを作ってみました。
https://play.google.com/store/apps/details?id=com.protogeo.moves

![Untitled3.gif](https://qiita-image-store.s3.amazonaws.com/0/39324/599cf509-9168-96c5-b1f5-a4d152604c27.gif "Untitled3.gif")

# ポイント

ポイントは、ボタンを離した時のポヨヨンと動く感じです。カスタムした Interpolator を使ったのですが、結果的にこんな感じになりました。はい、見たくないですね。

```java:CustomInterpolator.java
public class CustomInterpolator implements Interpolator {

    public CustomInterpolator() {
    }

    public float getInterpolation(float input) {
        if (input < 0.4) {
            return (float) ((Math.cos((2.5 * input + 1) * Math.PI) / 2.0f) + 0.5f) * 2f;
        } else if (input < 0.8) {
            return (float) ((Math.cos((2.5 * input + 1) * Math.PI) / 2.0f) + 0.5f) * 1.5f + 0.5f;
        } else {
            return (float) ((Math.cos((2.5 * input + 1) * Math.PI) / 2.0f) + 0.5f) * 1f + 0.5f;
        }
    }
}
```

このへんを参考にして作りました。
http://cogitolearning.co.uk/?p=1078
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.0.0_r1/android/view/animation/AccelerateDecelerateInterpolator.java#AccelerateDecelerateInterpolator

グラフはこんな感じです。ちなみにこのグラフは、Mac の Grapher というアプリを使って描画しました。

![graph.png](https://qiita-image-store.s3.amazonaws.com/0/39324/16f73ee3-b1ba-b438-2ddf-26f35b34a3a3.png "graph.png")


# コード

実際に使うなら Activity や Fragment には書きたくないですね。適当なクラスを作って、クリックイベントのコールバック用インタフェースを用意すればといいと思います。

もう少しいい方法があるんじゃないかなぁという気がしなくもないです。

```java:SampleActivity.java
final Button button = (Button) findViewById(R.id.button);
final Animation pathDown = AnimationUtils.loadAnimation(this, R.anim.path_down);
final Animation pathCancel = AnimationUtils.loadAnimation(this, R.anim.path_cancel);
final Animation pathUp = AnimationUtils.loadAnimation(this, R.anim.path_up);
pathUp.setInterpolator(new CustomInterpolator());

button.setOnTouchListener(new View.OnTouchListener() {

    private boolean cancelled = false;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                button.startAnimation(pathDown);
                break;

            case MotionEvent.ACTION_MOVE:
                if (!cancelled) {
                    // ボタンからフォーカスが外れた場合
                    if (event.getX() < 0
                            || v.getWidth() < event.getX()
                            || event.getY() < 0
                            || v.getHeight() < event.getY()) {
                        cancelled = true;
                        button.startAnimation(pathCancel);
                    }
                }
                break;

            case MotionEvent.ACTION_UP:
                if (cancelled) {
                    cancelled = false;
                } else {
                    button.startAnimation(pathUp);
                    // クリックイベントの処理
                }
                break;
        }

        return true;
    }
});
```

```xml:anim/path_down.xml
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="100"
    android:fillAfter="true"
    android:fromXScale="1.0"
    android:fromYScale="1.0"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="0.85"
    android:toYScale="0.85" />
```

```xml:anim/path_cancel.xml
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="100"
    android:fillAfter="true"
    android:fromXScale="0.85"
    android:fromYScale="0.85"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1"
    android:toYScale="1" />
```

```xml:anim/path_up.xml
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="480"
    android:fillAfter="false"
    android:fromXScale="0.85"
    android:fromYScale="0.85"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1"
    android:toYScale="1" />
```