1. 程式人生 > >RecyclerView 和 ListView 使用對比分析(佈局、API、巢狀滾動機制)

RecyclerView 和 ListView 使用對比分析(佈局、API、巢狀滾動機制)

空資料處理

ListView 提供了 setEmptyView 這個 API 來讓我們處理 Adapter 中資料為空的情況,只需輕輕一 set 就能搞定一切。程式碼設定和效果如下

        mListView = (ListView) findViewById(R.id.listview);
        mListView.setEmptyView(findViewById(R.id.empty_layout));//設定內容為空時顯示的檢視

而 RecyclerView 並沒有提供此類 API,所以,這些工作需要自己來幹。雖說這類邏輯並不複雜,但是作為一個有追求的程式猿,能偷懶還是要想著偷懶的嘛...



HeaderView 和 FooterView

在 ListView 的設計中,存在著 HeaderView 和 FooterView 兩種型別的檢視,並且系統也提供了相應的 API 來讓我們設定


使用 HeaderView 和 FooterView 的好處在於,當我們指向在 ListView 的頭部或者底部新增一個 View 的時候(例如:新增一個下拉重新整理檢視,底部載入更多檢視),我們可以不用影響到 Adapter 的編寫,使用起來相當方便。而到了 RecyclerView 中,翻來翻去你都不會看到類似 addFooterView 、 addFooterView 這種 API,是的,沒錯,壓根就沒有...這也是 RecyclerView 讓我覺得很雞肋的地方,按道理說應該是使用頻率很高的 API,居然都不給我(一臉懵逼)。那有木有解決方法呢,肯定有,系統不給就自己動手豐衣足食唄。我想到的方法比較笨,就是在 Adapter 中提供三種類型(Header,Footer以及普通Item)的 Type 和 View,但是這種方法寫起來很麻煩,對 Adapter 的影響很大,改動的程式碼量多並且也容易產生BUG。這裡需要吹一下鴻洋老師的解決方案了,大家可以看他的文章:

優雅的為RecyclerView新增HeaderView和FooterView 。他的實現思路是通過裝飾者模式來擴充 Adapter 的功能,從而實現新增 HeaderView 和 FooterView,並且不影響 Adapter 的編寫工作,牛逼的是還能支援多個 HeaderView 和 FooterView (雖然我暫時想不到有什麼應用場景,哈哈,不過先記著,以後說不定有用)。這是我目前看到的最贊成的方案了,如果你有更 nice 的方案,也歡迎給我留言。




區域性重新整理

在 ListView 中,說到重新整理很多童鞋會記得 notifyDataSetChanged() ,但是說到區域性重新整理估計有很多童鞋就知道得比較少了。我們知道在更新了 ListView 的資料來源後,需要通過 Adapter 的 notifyDataSetChanged 來通知檢視更新變化,這樣做比較的好處就是呼叫簡單,壞處就是它會重繪每個 Item,但實際上並不是每個 Item 都需要重繪。最常見的,例如:朋友圈點贊,點贊只是更新當前點讚的Item,並不需要每個 Item 都更新。然而 ListView 並沒有提供區域性重新整理重新整理某個 Item 的 API 給我們,同樣自己自足,套路大致如下方的 updateItemView:

public class AuthorListAdapter extends BaseAdapter {

    ...

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ...
        return convertView;
    }

    /**
     * 更新Item檢視,減少不必要的重繪
     *
     * @param listView
     * @param position
     */
    public void updateItemView(ListView listView, int position) {
        //換算成 Item View 在 ViewGroup 中的 index
        int index = position - listView.getFirstVisiblePosition();
        if (index >= 0 && index < listView.getChildCount()) {
            //更新資料
            AuthorInfo authorInfo = mAuthorInfoList.get(position);
            authorInfo.setNickName("Google Android");
            authorInfo.setMotto("My name is Android .");
            authorInfo.setPortrait(R.mipmap.ic_launcher);
            //更新單個Item
            View itemView = listView.getChildAt(index);
            getView(position, itemView, listView);
        }
    }

}

即可實現重新整理單個 Item 的效果



RecyclerView.Adapter 則我們提供了 notifyItemChanged 用於更新單個 Item View 的重新整理,我們可以省去自己寫區域性更新的工作。


實現效果如下


動畫效果

如果你細心觀察上面 ListView 和 RecyclerView 區域性更新 Item 的效果,你會發現相比 ListView 而言, RecyclerView 在做區域性重新整理的時候有一個漸變的動畫效果。這也是 RecyclerView 相對非常值得一提的地方,作為 ListView 自身並沒有為我們提供封裝好的 API 來實現動畫效果切換。所以,如果要給 ListView 的 Item 加動畫,我們只能自己通過屬性動畫來操作 Item 的檢視。 Github 也有很多封裝得好好的開源庫給我們用,如:ListViewAnimations 就封裝了大量的效果供我們玩耍,童鞋們可以自行學習一下



ListViewAnimations 主要大致實現方式是通過裝飾者模式來擴充 Adapter ,並結合屬性動畫 Animator 來新增動畫效果。相比之下,RecyclerView 則為我們提供了很多基本的動畫 API ,如下方的增刪移改


簡單的呼叫即可實現相應的效果,用起來方便很多,視覺互動上也會更好些




如果你對動畫效果有追求,覺得系統提供的並不能滿足你的需求,也可以通過相應介面實現自己的動畫效果,方式也非常簡單,繼承 RecyclerView.ItemAnimator 類,並實現相應的方法,再呼叫 RecyclerView 的 setItemAnimator(RecyclerView.ItemAnimator animator) 方法設定完即可實現自定義的動畫效果。


系統也為我們提供了兩個預設的動畫實現:SimpleItemAnimator 和 DefaultItemAnimator。而 RecyclerView 在不手動呼叫 setItemAnimator 的情況下,則預設用了內建的 DefaultItemAnimator 。




當然編寫自定義的 ItemAnimator 也是需要一定工作量的,這裡同樣為大家介紹一個針對 RecyclerView 開源的動畫庫:recyclerview-animators。其內部封裝了大量的動畫效果給供我們呼叫。