ListView底部item飛入動畫效果

最近有朋友展示了一種效果,就是ListView在滑動的過程中新加入的item會有一個從底部滑入的效果,我感覺這種效果還算不錯,就去想了想拿到我身上應該怎麼去實現這種效果,在試過幾種方案後,最後選擇了一種使用起來還算比較簡單的方式拿出來分享一下。
在開始分享之前,先來看看我們需要做成什麼效果吧,

恩,看到什麼效果了嗎?仔細看滑動過程中的底部,新加入的item會以一種動畫的形式加入,馬上,我們就來實現這種效果!

實現方式的選擇

當我第一次看到這種效果的時候,我首先想到的是LayoutAnimation,不過很快就讓我否決了,爲什麼呢?我們能的動畫只是在新加入的item的起到效果,而其他的item是沒有這個效果的,這種方式用LayoutAnimation是做不到的, 那我們應該怎麼實現呢?關鍵點就在動畫在什麼時候有效!前面我們說過很多次了,

新加入的item會以一種動畫的形式加入

我們只要抓住這一點往下想,很快就能想到AdaptergetView上,在新item加入的時候,Adapter的getView必定是去執行的,我們沿着這個思路往下走,很快我們又遇到問題了,

我們怎麼判斷已經滑動到底部了?

很多人會不假思索的回答,這個問題容易! 這不和ListView的分頁一樣嗎!當然不是了, ListView分頁的底部是判斷的數據集的最後一項,不太適用我們這裏,而且縱觀ListView的幾個方法,我們並沒有找到合適的方法去使用,所以,我們只能自己去判斷了,怎麼判斷?還是在監聽的OnScrollListener裏,在這裏面判斷是不是往下滑動的,至於是不是到達底部了,交給getView去做!所以我們還需要在Adapter中知道ListView的存在,並給他設置滑動監聽。

假設,現在我們已經可以可以判斷滑動到底部了,我們用一個狀態變量isScrollDown來表示,那是不是現在我們就可以在 getView裏通過isScrollDown來判斷是否給convertView一個動畫呢?

基本的實現思路已經闡述完了,下面我們就來着手用代碼來實現我們的思路,我們需要解決以下問題,

  1. 儘量將這些代碼封裝好,以避免每次使用的時候都copy一遍代碼
  2. 具體用代碼怎麼判斷ListView往下滑動

對於第一個問題,我們採用大多數情況下使用的方式,就是:

自定義一個抽象的Adapter,實現BaseAdapter中的getView方法,並定義一個buildView
來代替getView的功能。

這樣做的好處就是,我們可以在getView中做我們想做的事,而不必在意convertView怎麼形成。這也非常符合我們的需求,所以代碼可以這麼寫,

public abstract class BaseFlyAdapter extends BaseAdapter {

    public View getView(int position, View convertView, ViewGroup parent) {
        View view = buildView(position, convertView, parent);
        return view;
    }

    public abstract View buildView(int position, View convertView, ViewGroup parent);
}

上面說了,我們需要監聽ListView的滑動,這裏我們在BaseFlyAdapter中也一塊搞定!那我們的BaseFlyAdapter還需要一個ListView,並給他設置OnScrollListener

public abstract class BaseFlyAdapter extends BaseAdapter {

  private ListView mListView;

  public void bindView(ListView listView) {
        mListView = listView;
        mListView.setOnScrollListener(mScrollListener);
    }

    private OnScrollListener mScrollListener = new OnScrollListener() {
        public void onScrollStateChanged(AbsListView view, int scrollState) {
        }

        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
        }
    };

  ...
}

準備好了這些,我們就來實現如果判斷往下滑動這個需求,先上代碼,

public abstract class BaseFlyAdapter extends BaseAdapter {  
  private ListView mListView;

  private boolean isScrollDown; // 是否往下滑動
    private int mFirstPosition; // 第一個可見item的位置
    private int mFirstTop; // 第一個可以item的top值

  public void bindView(ListView listView) {
        mListView = listView;
        mListView.setOnScrollListener(mScrollListener);
    }

    private OnScrollListener mScrollListener = new OnScrollListener() {
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if(scrollState == OnScrollListener.SCROLL_STATE_IDLE) isScrollDown = false;
        }

        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
            View firstChild = view.getChildAt(0);
            if(firstChild == null) return;
            int top = firstChild.getTop();
            isScrollDown = firstVisibleItem > mFirstPosition || mFirstTop > top;
            mFirstTop = top;
            mFirstPosition = firstVisibleItem;
        }
    };
}

具體的代碼都是mScrollListener中,首先來看看onScrollStateChanged,這裏面的代碼很簡單,就是在滑動停止的時候去恢復isScrollDown變量。最重要的代碼還是在onScroll中。下面我們來具體看看這裏面的實現。

首先我們獲取ListView的第一個view——firstChild,如果這裏你不明白怎麼回事的話,可以去看看ListView的複用機制,接着我們來獲取到firstChild的top值,這些都是爲下面去判斷是不是往下滑動做準備的,那怎麼判斷呢?

isScrollDown = firstVisibleItem > mFirstPosition || mFirstTop > top;

一個的操作,這裏要考慮兩種情況,

  1. 新的item加入的時候,第一個item已經滑動出屏幕外。
  2. 當新的item加入,但第一個item還沒滑動出屏幕外。

對於第一種情況,是常規的一種情況,我們直接通過判斷第一個可見項,注意這裏的第一個可見項是指在我們數據集中的,並且判斷是不是大於我們之前保存的firstVisibleItem就ok, 可以看一下最後面的代碼,我們去保存了firstVisibleItem和第一個View的top值。對於第二種情況,我們只需要判斷View的top值是不是大於我們最後一次保存的值就ok。

ok,到現在爲止,我們可以知道ListView是不是往下滑動了,下面就開始給新加入的item添加動畫吧。爲了靈活,我們將動畫的定義放到外部,所以我們還需要給BaseFlyAdapter一個方法去設置動畫,

public abstract class BaseFlyAdapter extends BaseAdapter {
    private AnimationSet mAnimationSet;
    ...
    public void setAnimation(AnimationSet set) {
            mAnimationSet = set;
    }
    ...
}

動畫設置好了,我們最後就來看看在getView中怎麼做吧。上面說了,動畫的執行是在getView中,

public abstract class BaseFlyAdapter extends BaseAdapter {

  ...

  public View getView(int position, View convertView, ViewGroup parent) {
    View view = buildView(position, convertView, parent);
    if(isScrollDown && mAnimationSet != null) {
      cancelAnimation();
      view.startAnimation(mAnimationSet);
    }

    return view;
  }

  private void cancelAnimation() {
    int count = mListView.getChildCount();
    for(int i=0;i<count;i++) {
      mListView.getChildAt(i).clearAnimation();
    }
  }
}

首先調用buildView,以前我們在getView中寫的代碼,現在需要放到buildView中了,然後我們去判斷現在是不是往下滑動,如果是往下滑動,首先cancel掉所有item的動畫,這樣做的目的是防止在某個瞬間多個item執行動畫,然後直接調用convertView.startAnimation來開始動畫。不過,還記得我們在上面的一句話嗎?

至於是不是到達底部了,交給getView去做!

我們看getView的代碼裏也沒有關於解決這個問題的代碼啊!對,這沒有錯,getView執行了,那肯定是新的item加入了,而且我們在getView中做了是不是往下滑動的判斷了,所以這個問題自然就解決了!

ok,ok, 萬事俱備了,下面就讓我們開始使用一下吧,

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final ListView listView = (ListView) findViewById(R.id.list);
        MyAdapter adapter = new MyAdapter();
        adapter.bindView(listView);
        adapter.setAnimation((AnimationSet) AnimationUtils.loadAnimation(this, R.anim.anim));
        listView.setAdapter(adapter);
    }
}

先去無視MyAdapter的代碼, 它肯定是繼承了我們定義的BaseFlyAdapter, 我們通過setAnimation來設置了一個動畫,我們先來看看這個動畫怎麼寫的吧,

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="500"
        android:fromYDelta="50%"
        android:toYDelta="0" />
</set>

不多說,一個簡單的translate動畫。
最後,我們再來看看MyAdapter吧,其實和我們繼承BaseAdapter一樣只不過我們不需要書寫getView的代碼,而是放到buildView中,

class MyAdapter extends BaseFlyAdapter {

    public int getCount() {
        return 100;
    }

    public Object getItem(int position) {
        return "hello";
    }

    public long getItemId(int position) {
        return position;
    }

    @Override
    public View buildView(int position, View convertView, ViewGroup parent) {
        if(convertView == null) {
            convertView = View.inflate(parent.getContext(), R.layout.item, null);
        }
        return convertView;
    }
}

好了,到現在你可以看一下效果啦,當然這裏我們把動畫的定義抽出來了,這樣做的好處就是可以隨意切換動畫,現在我們修改動畫爲alpha動畫,

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <alpha
        android:duration="500"
        android:fillAfter="true"
        android:fromAlpha="0.0"
        android:toAlpha="1.0"/>
</set>

而我們的代碼不需要改變,再來看看效果,

很輕鬆,一個alpha的效果就完成了。就到這裏吧。

代碼下載,戳這裏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章