【Android】アニメーション付き開閉Viewの作り方

# 何がしたいか
・Viewをタップしたときにアニメーションに合わせてExpandしたりCollapseしたい

こんな感じ。
![expandable.gif](https://qiita-image-store.s3.amazonaws.com/0/132699/62f6172f-7864-7902-ddc1-9d4cc26f8882.gif)

ViewのVisibiltyを切り替えればExpandしたりCollapseできるけど、アニメーションは付けられない。
View#animate()とかで開閉するアニメーションをしても上手く行きません。詳しくは後述。

## 結論
・ViewをリサイズするためにAnimationクラスを継承したResizeAnimationを作る
参考：
http://stackoverflow.com/questions/8063466/how-to-expand-a-layout-height-with-animation

## animate()やstartAnimation()でアニメーションしたとき
実はViewのサイズや位置情報は変更されてないのです。
だから、Layoutをこんな風にして

```animeate_layout.xml
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#ef5350"
        android:id="@+id/content">

        <TextView
            android:id="@+id/text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:text="タイトル：えきすぱんどView"/>

        <TextView
            android:id="@+id/expandable_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/body"
            android:layout_below="@+id/text_view"
            android:layout_centerHorizontal="true"/>

    </RelativeLayout>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="動かないよー"
        android:id="@+id/button"
        android:layout_gravity="center_horizontal"/>

</LinearLayout>
```

んでもって、

```MainActivity.java
public class MainActivity extends AppCompatActivity {

    private TextView expandableView;
    private View content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.animate_layout);
        content = findViewById(R.id.content);
        expandableView = (TextView) findViewById(R.id.expandable_view);

        content.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 開くようなアニメーション
                if(expandableView.getVisibility() != View.VISIBLE) {
                    expandableView.animate().alphaBy(0).alpha(1).translationYBy(-10).translationY(0).withStartAction(new TimerTask() {
                        @Override
                        public void run() {
                            // アニメーションが始まる前にViewをVISIBLEにする
                            expandableView.setVisibility(View.VISIBLE);
                        }
                    });
                }
                // 開じるようなアニメーション
                else {
                expandableView.animate().alpha(0).translationY(-10).withEndAction(new TimerTask(){
                        @Override
                        public void run() {

                            // アニメーションが終わったらViewをGONEにする
                            expandableView.setVisibility(View.GONE);
                        }
                    });
                }
            }
        });
    }
```

こんな風にしてButtonを押すと開閉するアニメーションを作る。

で、ボタンをタップしてみると・・・。
![no_work.gif](https://qiita-image-store.s3.amazonaws.com/0/132699/8f64b03f-9af7-ba07-1c35-1225d45fce95.gif)

上手く行きませんね。

今度は上下に移動するようなアニメーションも試してみます。

![no_work2.gif](https://qiita-image-store.s3.amazonaws.com/0/132699/7eb011a1-1e85-a2e2-90ea-6a50e428676c.gif)

これもダメ。

理由は先述の通りアニメーションは見かけ上が変化しているだけで
実際のViewのサイズ、位置情報に変化がないから。

これをViewの実サイズもリサイズして冒頭のアニメーションを実現したい。

## ResizeAnimationを作る
ViewのサイズをリサイズするようにAnimationクラスを継承してカスタムします。

以下のようにします。

```ResizeAnimation.java
public class ResizeAnimation extends Animation {
    final int addHeight;
    View view;
    int startHeight;

    public ResizeAnimation(View view, int addHeight, int startHeight) {
        this.view = view;
        this.addHeight = addHeight;
        this.startHeight = startHeight;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        int newHeight = (int) (startHeight + addHeight * interpolatedTime);
        view.getLayoutParams().height = newHeight;
        view.requestLayout();
    }

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
    }

    @Override
    public boolean willChangeBounds() {
        return true;
    }
}
```
ResizeAnimationのコンストラクタの
第一引数はアニメーションさせたいView
第二引数はアニメーション後に追加する高さ
第三引数はアニメーション開始時の高さ
を取ります。

例えば、アニメーション後に高さを0にしたい場合は
第二引数は -startHeightになる。

これをさっきのMainActivityで以下のように使う。
Animationクラスを継承しているので、Interpolatorとかももちろん設定できる。
setDurationを設定しないと上手くいかないので必ず設定すること。
（あるいはコンストラクタで適当なデフォルト値を入れるようにする）
今回はsetDurationを300に設定。

```MainActivity.java
public class MainActivity extends AppCompatActivity {

    private TextView expandableView;
    private int originalHeight;
    private View content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.animate_layout);
        content = findViewById(R.id.content);
        expandableView = (TextView) findViewById(R.id.expandable_view);
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        // ExpandするViewの元のサイズを保持する
        originalHeight = expandableView.getHeight();

        // ビューを閉じるアニメーションを生成
        final ResizeAnimation collapseAnimation = new ResizeAnimation(expandableView, -expandableView.getHeight(), expandableView.getHeight());
        collapseAnimation.setDuration(300);
        // ビューを開くアニメーションを生成
        final ResizeAnimation expandAnimation = new ResizeAnimation(expandableView, originalHeight, 0);
        expandAnimation.setDuration(300);

        content.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                expandableView.clearAnimation();
                if(expandableView.getHeight() > 0) {
                    expandableView.startAnimation(collapseAnimation);
                } else{
                    expandableView.startAnimation(expandAnimation);
                }
            }
        });

    }
}
```

ここで気をつけないといけないのは
onCreateの中ではViewのサイズ情報は取得できないこと。（getHeightとかしても0になる）
onWindowFocusChangedの中で呼んであげればちゃんと取得することができる。

アニメーションするとViewのサイズが変わってしまうので、
アニメーションする前に元のサイズを保持しておく必要がある。

実際にできるかどうか試してみよう。

![expandable.gif](https://qiita-image-store.s3.amazonaws.com/0/132699/62f6172f-7864-7902-ddc1-9d4cc26f8882.gif)

ちゃんと冒頭のようになった！

今回の例はResizeAnimationに高さしか指定しないコンストラクタを作成したけど、
もちろん横幅を指定したり、座標を指定するコンストラクタを作れば、高さと同じように横幅を変えたり、位置を変えたりもできる。