1. 程式人生 > >Android RecyclerView與ListView比較

Android RecyclerView與ListView比較

gets 功能 itemid nested dem 集合 title fyi 這一

RecyclerView 概述

RecyclerView 集成自 ViewGroup 。RecyclerView是Android-support-V7版本中新增的一個Widgets,官方對於它的介紹是:RecyclerView是ListView的升級版本,更加先進和靈活。

Android L 之後,Google 提供了RecyclerView視圖化控件,5.0之前如果想要使用的話,可以添加V7包以向下兼容,提供更全面的API和更靈活的布局管理。

RecyclerView 做了什麽

  • 類似ListView;
  • 類似GridView;
  • 橫向ListView;
  • 橫向GridView;
  • 瀑布流式布局

RecyclerView 組成

  • RecyclerView.LayoutManager
  • RecyclerView.Recyler
  • RecyclerView.Adapter
  • RecyclerView.ViewHolder
  • RecyclerView.ItemDecoration
  • RecyclerView.ItemAnimator

機制:layoutmanager 從Recycle 中獲取已經綁定數據的 Item 顯示,並將不再需要的Item 丟給Recycler 回收;Adapter 負責生成新Item 並將其綁定好數據,供Recyle獲取;Recycler 就是子 Item 的一個緩存池。

RecyclerView.LayoutManager -- 管理子View布局的一個組件

主要負責:布局子視圖、滾動子視圖在滾動過程中根據子視圖在布局中所處的位置,決定何時添加子視圖和刪除子視圖。
涉及到的API:

  • 獲取布局尺寸
    setRecyclerView()、setMeasureSpecs()、setMeasuredDimensionFromChildren()
  • 可以設置和獲取方向的水平或垂直
    canScrollHorizontally()、canScrollVertically()、setOrientation()、getOrientation()
  • 滑動狀態改變時RecyclerView調用方法通知LayoutManager
    onScrollStateChanged(()
  • 增、刪、移動子View
    addView()、removeView()、moveView()、removeAllViews()、removeViewAt()
  • 獲取指定位置的View
    getPosition()、getChildAt()、findViewByPosition()
  • 獲取可見子View及全部子View的個數
    getChildCount()、getItemCount()
  • 移除數據後調用了recycler進行數據的緩存
    detachView()、detachAndScrapAttachedViews()、removeAndRecycleAllViews()、scrapOrRecycleView()

RecyclerView.Adapter 負責數據、Item的生成和數據的綁定

Adapter 有幾個抽象方法需要子類實現:

  • 返回一個ViewHolder 封裝實例
    onCreateViewHolder()
  • 根據ViewHolder對應的View進行數據綁定
    onBindViewHolder()
  • 獲取總數
    getItemCount()
  • 獲取不同Type類型的View,為添加 header 和 footer 預留接口
    getItemViewType()
  • 指定位置的 item 內容發生了變化
    notifyItemChanged()
  • 在指定的位置處插入一個 Item
    notifyItemInserted()
  • 指定位置的兩個 Item 進行交換
    notifyItemMoved(int, int)

RecyclerView.Recyler 負責 Item 的緩存

即提供新的,也回收舊的(強大就強大在View的循環回收利用)

  • RecyclerView 的二級緩存:
    有兩個緩存:Scrap 和 Recycle ,Scrap 中文是廢料的意思。Recycle 對應是回收的意思。
    Scrap 緩存是指裏面緩存的View 是接下來需要用到的,不需要新綁定數據,是一個輕量級的緩存集合,而Recycle 的緩存的 View 為裏面的數據需要重新綁定,都放在RecyclerViewPool 池中,都需要通過 Adapter 重新綁定數據。
  • RecyclerView 緩存的兩種方式:
    Detach 和 Remove ,Detach 的View 放在Scrap 緩存中,Remove 掉的View 放在 RecyclerViewPool緩存池中。
  • 使用場景:
    反復去將View移除並且馬上又要添加進去時,選擇Detach 方式,通過方法 detachAndScrapView()實現。使用頻率很低,屏幕中不顯示的時候使用Remove 的方式,通過方法 removeAndRecycleView()實現。
  • 復用流程:
    當我們去獲取一個新的View時,首先去檢查Scrap 緩存是否有對應的 position 的View ,如果有直接用;如果沒有,則從RecyclerViewPool緩存池中取,並且會回調Adapter 的onBindViewHolder 方法(如果Recycle 緩存為空,還會調用onCreateViewHolder方法),最後再將綁定好新數據的View返回。

相關方法

與Recycler相關:

  • 獲取緩存最大閥值,閥值為2
    setItemViewCacheSize()
  • 從緩存中取Item
    getViewForPosition()
  • 獲取Scrap 緩存列表
    getScrapList()
  • 從Layoutmanager 回收Item
    recyclerView()
  • 回收後,緩存類型的內部處理邏輯
    recyclerViewHolderInternal()
  • 根據Adapter 變化,轉換Item 緩存如pool
    onAdapterChanged()
  • 調用此方法返回一個RecyclerViewPool 實例
    getRecyclerView()
  • 緩存如pool
    addViewHolderToRecyclerViewPool()

RecyclerViewPool 相關:

  • 緩存的不同類型ViewType的數量是不限的,但是每個viewType 的具體ViewHolder 最多為5個
    setMaxRecycledViews(int viewType,int max)
  • 存入viewHolder
    putRecycledView()
  • 。。。
    getRecycledView()

RecyclerViw 與 listView 比較

  • Item 回收/復用方面:後者是以convertView 作為回收單位,需要手動添加ViewHolder ,而前者則是以ViewHolder作為回收單位,convertView 被內置到了ViewHolder 中作為 ViewHolder 的成員變量,前者內置了Recycle 、多級緩存。
  • 樣式豐富方面:前者通過支持水平、垂直和變革列表及其他更復雜形式,而後者只支持具體某一種
  • 效果增強方面:前者內置了ItemDecoration 和 ItemAnimator ,可以自定義繪制 itemView 之間的一些特殊UI 或Item 項數據變化時的動畫效果,而yoga後者實現比較麻煩。
  • 代碼內聚方面:前者將功能密切相關的類攜程內部類,如ViewHolder,Adapter。而後者沒有。

adapter用法

  • ListView 的 adapter
class MyAdapter extends BaseAdapter {    
    ……    
    @Override    
    public View getView(int position, View convertView, ViewGroup parent) {    
       ViewHolder holder = null;    
        if (convertView == null) {    
            holder = new ViewHolder();    
            …… //初始化convertView和ViewHolder    
            convertView.setTag(holder);    
        } else {    
            holder = (ViewHolder) convertView.getTag();    
        }    
       ……    
    }    
}    
static class ViewHolder {    
   TextView txvTitle;    
} 
  • RecyclerView 的 adapter
class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

       @Override
       public int getItemCount() {
           return 0;
       }

       @Override
       public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
           return null;
       }

       @Override
       public void onBindViewHolder(MyViewHolder holder, int position) {

       }

       class MyViewHolder extends RecyclerView.ViewHolder {
           //TODO 初始化控件   
           
           public MyViewHolder(View itemView) {
               super(itemView);
           }
       }
    }

最基礎的adapter,應用中列表會有許多,有多少個列表就會有幾個適配器,可以根據需求封裝adapter,以方便使用。

布局效果對比

作為一枚控件,要引起開發者使用的欲望自然先是從顯示效果看起(看臉的世界),ListView 大家對效果已經很熟悉了,這裏直接跳過,而作為 RecyclerView,它能帶給效果要比 ListView 強大得多,如下圖

技術分享圖片

Android 默認提供的 RecyclerView 就能支持 線性布局網格布局瀑布流布局 三種(這裏我們暫且不提代碼細節,後文再說),而且同時還能夠控制橫向還是縱向滾動。怎樣,從效果上足以碾壓 ListView 有木有。

  • 橫向滾動的ListView開源控件是不是可以不用再找了?對,你沒看錯!
  • 瀑布流效果的開源控件是不是可以不用再找了?對,你沒看錯!
  • 連橫向滾動的GridView都不用找了!對,你沒看錯!

到此,展示效果上的差距一目了然。

API 使用對比

當然,一個控件我們不能完全只看效果,關鍵還是要看實用性,看看有沒有方便我們調用的 API提高我們的開發效率。所以,接下來我們就從各個方面來看看 RecyclerView 和 ListView 在提供的API調用上的一些實踐比較。

基礎使用

ListView 的基礎使用大家再熟悉不過,其使用的關鍵點主要如下:

  • 繼承重寫 BaseAdapter 類
  • 自定義 ViewHolder 和 convertView 一起完成復用優化工作

由於 ListView 已經老生常談,所以此處就不去寫示例代碼了。 RecyclerView 基礎使用關鍵點同樣有兩點:

  • 繼承重寫 RecyclerView.Adapter 和 RecyclerView.ViewHolder
  • 設置布局管理器,控制布局效果

示例代碼大致如下:


// 第一步:繼承重寫 RecyclerView.Adapter 和 RecyclerView.ViewHolder
public class AuthorRecyclerAdapter extends RecyclerView.Adapter<AuthorRecyclerAdapter.AuthorViewHolder> {

    ...

    @Override
    public AuthorViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ...
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(AuthorViewHolder holder, int position) {
        ...
    }

    @Override
    public int getItemCount() {
        if (mData == null) {
            return 0;
        }
        return mData.size();
    }

    class AuthorViewHolder extends RecyclerView.ViewHolder {

        ...

        public AuthorViewHolder(View itemView) {
            super(itemView);
            ...

        }
    }
}

mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerAdapter = new AuthorRecyclerAdapter(mData);

// 第二步:設置布局管理器,控制布局效果
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(RecyclerDemoActivity.this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(linearLayoutManager);

mRecyclerView.setAdapter(mRecyclerAdapter);

從基礎使用上看,我們明顯可以看出,RecyclerView 相比 ListView 在基礎使用上的區別主要有如下幾點:

  • ViewHolder 的編寫規範化了
  • RecyclerView 復用 Item 的工作 Google 全幫你搞定,不再需要像 ListView 那樣自己調用 setTag
  • RecyclerView 需要多出一步 LayoutManager 的設置工作

布局效果

在最開始就提到,RecyclerView 能夠支持各種各樣的布局效果,這是 ListView 所不具有的功能,那麽這個功能如何實現的呢?其核心關鍵在於 RecyclerView.LayoutManager 類中。從前面的基礎使用可以看到,RecyclerView 在使用過程中要比 ListView 多一個 setLayoutManager 步驟,這個 LayoutManager 就是用於控制我們 RecyclerView 最終的展示效果的。

技術分享圖片

而 LayoutManager 只是一個抽象類而已,系統已經為我們提供了三個相關的實現類 LinearLayoutManager(線性布局效果)GridLayoutManager(網格布局效果)StaggeredGridLayoutManager(瀑布流布局效果)。如果你想用 RecyclerView 來實現自己 YY 出來的一種效果,則應該去繼承實現自己的 LayoutManager,並重寫相應的方法,而不應該想著去改寫 RecyclerView。關於 LayoutManager 的使用有下面一些常見的 API(有些在 LayoutManager 實現的子類中)


    canScrollHorizontally();//能否橫向滾動
    canScrollVertically();//能否縱向滾動
    scrollToPosition(int position);//滾動到指定位置

    setOrientation(int orientation);//設置滾動的方向
    getOrientation();//獲取滾動方向

    findViewByPosition(int position);//獲取指定位置的Item View
    findFirstCompletelyVisibleItemPosition();//獲取第一個完全可見的Item位置
    findFirstVisibleItemPosition();//獲取第一個可見Item的位置
    findLastCompletelyVisibleItemPosition();//獲取最後一個完全可見的Item位置
    findLastVisibleItemPosition();//獲取最後一個可見Item的位置

上面僅僅是列出一些常用的 API 而已,更多的 API 可以查看官方文檔,通常你想用 RecyclerView 實現某種效果,例如指定滾動到某個 Item 位置,但是你在 RecyclerView 中又找不到可以調用的 API 時,就可以跑到 LayoutManager 的文檔去看看,基本都在那裏。另外還有一點關於瀑布流布局效果 StaggeredGridLayoutManager 想說的,看到網上有些文章寫的示例代碼,在設置了 StaggeredGridLayoutManager 後仍要去 Adapter 中動態設置 View 的高度,才能實現瀑布流,這種做法是完全錯誤的,之所以 StaggeredGridLayoutManager 的瀑布流效果出不來,基本是 item 布局的 xml 問題以及數據問題導致。如果要在 Adapter 中設置 View 的高度,則完全違背了 LayoutManager 的設計理念了。

空數據處理

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。其內部封裝了大量的動畫效果給供我們調用。

技術分享圖片

如果想要學習怎麽寫一個自定義 ItemAnimator ,上面介紹的開源庫的代碼同樣不容錯過。哦,對了,如果談到動畫效果,還有一個很關鍵的類不得不提,那就是 ItemTouchHelper 。

技術分享圖片

ItemTouchHelper 是系統為我們提供的一個用於滑動和刪除 RecyclerView 條目的工具類,用起來也是非常簡單的,大致兩步:

  • 創建 ItemTouchHelper 實例,同時實現 ItemTouchHelper.SimpleCallback 中的抽象方法,用於初始化 ItemTouchHelper
  • 調用 ItemTouchHelper 的 attachToRecyclerView 方法關聯上 RecyclerView 即可

示例代碼大致如下:


    //ItemTouchHelper 用於實現 RecyclerView Item 拖曳效果的類
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {

            @Override
            public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
                //actionState : action狀態類型,有三類 ACTION_STATE_DRAG (拖曳),ACTION_STATE_SWIPE(滑動),ACTION_STATE_IDLE(靜止)
                int dragFlags = makeFlag(ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP | ItemTouchHelper.DOWN
                        | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);//支持上下左右的拖曳
                int swipeFlags = makeMovementFlags(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);//表示支持左右的滑動
                return makeMovementFlags(dragFlags, swipeFlags);//直接返回0表示不支持拖曳和滑動
            }

            /**
             * @param recyclerView attach的RecyclerView
             * @param viewHolder 拖動的Item
             * @param target 放置Item的目標位置
             * @return
             */
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                int fromPosition = viewHolder.getAdapterPosition();//要拖曳的位置
                int toPosition = target.getAdapterPosition();//要放置的目標位置
                Collections.swap(mData, fromPosition, toPosition);//做數據的交換
                notifyItemMoved(fromPosition, toPosition);
                return true;
            }

            /**
             * @param viewHolder 滑動移除的Item
             * @param direction
             */
            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                int position = viewHolder.getAdapterPosition();//獲取要滑動刪除的Item位置
                mData.remove(position);//刪除數據
                notifyItemRemoved(position);
            }

        });
        itemTouchHelper.attachToRecyclerView(mRecyclerView);

雖然代碼中有註釋,但還是稍稍解釋一下,主要重寫的是 getMovementFlags 、 onMove 、 onSwiped 三個抽象方法,getMovementFlags 用於告訴系統,我們的 RecyclerView 到底是支持滑動還是拖曳。如上面的示例代碼,就是表示著同時支持上下左右四個方向的拖曳和左右兩個方向的滑動效果。如果時滑動,則 onSwiped 會被回調,如果是拖曳 onMove 會被回調。我們再到其中實現相應的業務操作即可。最終效果如下

技術分享圖片

想想我們以前用 ListView 的時候要怎麽做,RecyclerView 真的爽多了。

監聽 Item 的事件

ListView 為我們準備了幾個專門用於監聽 Item 的回調接口,如單擊、長按、選中某個 Item 等

技術分享圖片

說實話,其實我並不大喜歡這樣的設計,如 setOnItemClickListener ,在我們不添加 HeaderView 和 FooterView 的時候,我們可以通過回調參數中的 position 去拿到數據源列表中對應 Item 的數據。

技術分享圖片

但是,添加了 HeaderView 和 FooterView 之後就不一樣了,ListView 會把 HeaderView 和 FooterView 算入 position 內。假設你原先在 onItemClick 回調方法中寫了 mDataList.get(position) 這樣的業務代碼並且這段代碼運行良好許久,但在某天你突然加了個 HeaderView 後,這段代碼就開始變的有問題了,此時因為 HeaderView 占用的位置算入了 position 之內,所以 position 的最大值實際上是大於 mDataList 包含元素的個數值的,因此代碼會報數組越界的錯誤。當然,我們可以去避免這種問題的發生,就是不通過 position 來獲取數據,二是通過回調方法中的 id 。

技術分享圖片

這樣就不會受到添加 HeaderView 和 FooterView 的影響了,這個 id 的值就是來自我們編寫好的 Adapter 中的 getItemId 函數中返回的 id,使用 IDE 生成此函數時,默認是返回0,需要將 position 作為 Item 的 id 返回。

技術分享圖片

並同時在 onItemClick 中判斷 id 是否值為 -1,因為 HeaderView 和 FooterView 的返回值就是 -1。前面講到我並不大喜歡 setOnItemClickListener 這種設計,除了由這些因素的影響外,更關鍵的是個人認為針對 Item 的事件實際上寫在 getView 方法中會更加合適,如 setOnItemClickListener 我更喜歡用在 getView 中為每個 convertView 設置 setOnClickListener 的方式去取代它。

而再來看看 RecyclerView ,它並沒有像 ListView 提供太多關於 Item 的某種事件監聽,唯一的就是 addOnItemTouchListener

技術分享圖片

API 的名字言簡意賅,就是監聽 Item 的觸摸事件。如果你想要擁有 ListView 那樣監聽某個 Item 的某個操作方法,可以看看這篇文章 RecyclerView無法添加onItemClickListener最佳的高效解決方案 ,作者的實現思路就是通過 addOnItemTouchListener 和系統提供的 GestureDetector 手勢判斷結合實現的。不過,我還是更喜歡原先自己用慣的方式,雖然會被人吐槽 new 出了大量的監聽器,但個人覺得這樣封裝會更好(哈哈,也換大家吐槽這種方式的其他劣處,看看我是不是需要改改了)。

OK,關於 RecyclerView 和 ListView 一些常用的功能和 API 的對比,就大致到此。最後再來談談 Android L 開始之後,對 RecyclerView 和 ListView 的使用存在什麽影響。

嵌套滾動機制

熟悉 Android 觸摸事件分發機制的童鞋肯定知道,Touch 事件在進行分發的時候,由父 View 向它的子 View 傳遞,一旦某個子 View 開始接收進行處理,那麽接下來所有事件都將由這個 View 來進行處理,它的 ViewGroup 將不會再接收到這些事件,直到下一次手指按下。而嵌套滾動機制(NestedScrolling)就是為了彌補這一機制的不足,為了讓子 View 能和父 View 同時處理一個 Touch 事件。關於嵌套滾動機制(NestedScrolling),實現上相對是比較復雜的,此處就不去拓展說明,其關鍵在於 NestedScrollingChildNestedScrollingParent 兩個接口,以及系統對這兩個接口的實現類 NestedScrollingChildHelperNestedScrollingParentHelper 大家可以查閱相關的資料。可能說起來太抽象了,這裏拿一個簡單的示例效果來說明好了,如下方是用 CollapsingToolbarLayout 和 RecyclerView 搭配的效果:

技術分享圖片

一開始上面一大塊區域就是 CollapsingToolbarLayout ,下方的列表是 RecyclerView ,當然 RecyclerView 向上滑動時,CollapsingToolbarLayout 能夠同時網上收縮,直到只剩下頂部的 Toolbar。之所以能夠實現這種效果,就是完全依賴於嵌套滾動機制,如果沒有這套機制,按照原有的觸摸事件分發邏輯, RecyclerView 內部已經把 Touch 事件消耗掉了,完全無法引起頂部的 CollapsingToolbarLayout 產生聯動收縮的效果。我們可以查看 RecyclerView 的代碼實現,發現它已經實現了 NestedScrollingChild 接口

技術分享圖片

如果在其他代碼布局都不變的情況下,我們把 RecyclerView 替換成 ListView ,則無法產生上面圖中的動態效果,因為 ListView 並不支持嵌套滾動機制,事件在 ListView 內部已經被消耗且無法傳遞出來,大家可以自行嘗試驗證一下。對下方 AppBarLayout 的使用也是同理。

技術分享圖片

關於 AppBarLayout 和 CollapsingToolbarLayout,它們並不是什麽第三方控件,而是 Android 官方提供的 MaterialDesign 設計風格的控件,大家可以在官方文檔中搜索到它們的資料,如果你用過 Android 原生系統,你可以在通訊錄等系統內置應用看到它們的身影。如果你想使用類似 AppBarLayout 、 CollapsingToolbarLayout 這種需要嵌套滾動的機制才能達到效果的控件,那麽 RecyclerView 將是你的不二之選,因為 ListView 在此根本無法發揮作用。同樣的,ScrollView 也是不支持嵌套滾動機制,但是你可以使用 NestedScrollView 。



作者:D_clock愛吃蔥花
鏈接:https://www.jianshu.com/p/f592f3715ae2
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並註明出處。

Android RecyclerView與ListView比較