標籤雲

搜尋此網誌

2013/09/01

Animation - ListView Animations

今天來研究一下 ListView 的刪除動畫

由於 ListView 捲動時會把畫面上的 item 重用以顯示不同資料
這樣會導致我們可能會刪除到非正確的 item
或是出現顯示上的問題(該 item 顯示的資料已經不同但是動畫卻還在它上面跑)

要解決這個問題就要使用 View 類別在 API 16 加入的新 method
public void setHasTransientState (boolean hasTransientState)
當設為 true 的時候,就告訴系統這個 View 應該盡可能的被保留,直到setHasTransientState(false)被呼叫
要注意這是可以重複設置的,也就是說每一個setHasTransientState(true)都要搭配一個setHasTransientState(false)才能回復

//Listview 的 OnItemClickListener 的內容
//本範例點了 item 後會淡出並刪除該 item
public void onItemClick(AdapterView parent, final View view, int position, long id) {
    final String item = (String) parent.getItemAtPosition(position);
    ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, 0);
    anim.setDuration(500);
    view.setHasTransientState(true); //設為 true 宣告 item 要被追蹤
    anim.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            myListview.remove(item);
            adapter.notifyDataSetChanged(); //重新整理 listview
            view.setAlpha(1);
            view.setHasTransientState(false); //完成後設定回 false
        }
    });
    anim.start();
}
另一種方法是用 ViewPropertyAnimator 來解決
public void onItemClick(AdapterView parent, final View view, int position, long id) {
    final String item = (String) parent.getItemAtPosition(position);
    view.animate()
        .setDuration(500)
        .alpha(0)
        .withEndAction(new Runnable() {
            @Override
            public void run() {
                myListview.remove(item);
                adapter.notifyDataSetChanged();
                view.setAlpha(1);
            }
        }
    );
}
但以上兩種方式最大的問題就是 API 16 以後才能使用(用到了 withEndAction 跟 setHasTransientState)
(該死的 Android Fragmentation...)

讓我們看看另一個例子
在 DevBytes: Animating ListView Deletion: Now on Gingerbread! 裡
他用了一個繼承自 ArrayAdapter 的自訂 adapter
override 掉 public boolean hasStableIds() 讓它總是回傳 true ,用來取代 setHasTransientState(true)

//用來取代 setHasTransientState(true)
@Override
public boolean hasStableIds() {
    return true; //總是回傳 true,用來取代 setHasTransientState(true)
}

而 withEndAction 的部分則用 setAnimationListener 取代
//API 11 之前的方法
TranslateAnimation translator = new TranslateAnimation(startX, endX, startY, endY);
translator.setDuration(150);
view.startAnimation(translator);
view.getAnimation().setAnimationListener(new AnimationListenerAdapter() {
    @Override
    public void onAnimationEnd(Animation animation) {
        new Runnable() {
            @Override
            public void run() {
                //...(略)
            }
        }
    }
});

這樣可以讓 Gingerbread(API 10) 或更早的 Android 版本相容
如果是 4.0 (API 14)之後的版本
還是用 view.animate() 跟 ObjectAnimator 來做
//API 小於 16 但大於等於 14 可以這樣做
view.animate().setDuration(150);
ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, startX, endX);
anim.setDuration(150);
anim.start();
setAnimatorEndAction(anim, new Runnable() {
    @Override
    public void run() {
        //...(略)
    }
});

相關資料:
YouTube - DevBytes: ListView Deletion
YouTube - DevBytes: ListView Animations
YouTube - DevBytes: Animating ListView Deletion
YouTube - DevBytes: Animating ListView Deletion: Now on Gingerbread!

沒有留言: