AndroidのListViewでアニメーションしながらセルを消す

何も考えずに ListView のセルを Adapter#remove() なんてやって削除すると、スパっとなんのアニメーションもなしに消えてくれる。
それを見て「あ〜なんとも Androidっぽいな〜」と思うのも良いのだけど、簡単にアニメーションできないものかな?と思ってちょとだけやってみたのでメモ。


[2013/01/08 updated]
増やす方もやってみたよ!

[2013/03/15 updated]
引っ張って更新もやってみたよ!

ScaleAnimation とかしてみても白いセルは残るよ!

まず考えたのが消したいセルの View を ScaleAnimation して上や下に縮めてみること。
確かにこれでもアニメーションして縮むのだけど、縮んだ部分が白いまま残って上や下のセルが追随して動いてくれない。

結果、アニメーション後に Adapter#notifyDataSetChanged() すると白いセルはなくなるのだけど、これじゃアニメーションの意味がない!

requestLayout() と willChangeBounds()

というわけで、困った時の Stack Overflow 頼み!
こんなん見つけた。

直接 ListView の話ではないのだけど、周りと一緒にアニメーションするにはどうしたら良いか?って感じの話なのかな?
ここで紹介されているコードを使ってみたら思い通りのアニメーションができた!


紹介されてたコードはこんなん。

public static void collapse(final View v) {
    final int initialHeight = v.getMeasuredHeight();

    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            if(interpolatedTime == 1){
                v.setVisibility(View.GONE);
            }else{
                v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
                v.requestLayout();
            }
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    // 1dp/ms
    a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
    v.startAnimation(a);
}

ただ、セルをアニメーションしながら潰すと、それ以降 View が再使用される時にも潰れたままになってしまい、Adapter#notifyDataSetChanged() した時も潰れたままで表示される。

結果、ListView の見た目的には2つのセルが消えた様に見える。(Dividerが2本表示されているので間に潰れたセルがあるのがわかる。)
上の絵だと、No.5は本当に remove() で消したセル。No.6は潰れた View を使って表示されているので見えていないセル。


そんなわけで、アニメーション終了時に潰れたセルの View を元に戻さなきゃいけないな〜と思ったのだけど、height を元のサイズに戻してみても真っ白いままで内容が表示されない…。

セルのアニメーション後にサイズを元に戻すのはここで行き詰まったので、結局、潰れたセルの View は再度 inflate することにした。

ListViewのセルがアニメーションしながら消える様になった〜!

private class ViewHolder {
    public boolean needInflate;
    public TextView text;
}

なんて感じで、View に ViewHolder をくっ付けてそこに再度 inflate が必要かどうかのフラグを持たせた。
View を再使用する際に needInflate が true なら、その潰れた View は使わずに新しく view を作っちゃう!(無駄遣い!)


これでなんとか思い通りの動作をするようにはなった〜。

しかし、このままだと潰れた View がキャッシュ?に残ってて何度も再使用されそうになる。
セルを消せば消すほど潰れた View が増えるので、どんどん無駄なキャッシュが増えていくことになるんだけど…。
要らないキャッシュを消すか、キャッシュにある潰れた View の height と内容を元に戻して表示できるようにする方法を探さないとダメだな〜。


というわけで、サンプルのコードは GitHub に置いておいたので、興味のある方はどうぞ。

もっと良い感じにアニメーションする方法とかあったら教えてもらえると嬉しいです!