RecyclerView與ListView

ListView作為一個流布局控件是一個比較老的資格了。
RecyclerView作為Desgin設(shè)計里面的一個新空間卻有著強(qiáng)大的功能顶燕,其支持線性布局、網(wǎng)格布局、瀑布流布局 三種辽幌,而且同時還能夠控制橫向還是縱向滾動。

基礎(chǔ)使用

ListView 的基礎(chǔ)使用大家再熟悉不過椿访,其使用的關(guān)鍵點主要如下:
繼承重寫 BaseAdapter 類
自定義 ViewHolder 和 convertView 一起完成復(fù)用優(yōu)化工作
由于 ListView 已經(jīng)老生常談乌企,所以此處就不去寫示例代碼了。

RecyclerView 基礎(chǔ)使用關(guān)鍵點同樣有兩點:
繼承重寫 RecyclerView.Adapter 和 RecyclerView.ViewHolder
設(shè)置布局管理器成玫,控制布局效果加酵。

// 第一步獲取控件
recyclerView = findViewById(R.id.home_recycler);
// 設(shè)置布局管理器拳喻,控制布局效果
LinearLayoutManager manager = new LinearLayoutManager();
recyclerView.setLayoutManager(manager);
//設(shè)置適配器
recyclerView.setApapter(new HomeRecyclerView(context ,itemDatas));

// 創(chuàng)建適配器
public class HomeRecyclerAdapter extends RecyclerView.Adapter<HomeRecyclerAdapter.HomeHolderView> {
    // 創(chuàng)建ViewHolder及綁定View
    @Override
    public HomeHolderView onCreateViewHolder(ViewGroup parent, int viewType) {
        return null;
    }

    // 多布局使用
    @Override
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }

    // 填充數(shù)據(jù),
    @Override
    public void onBindViewHolder(HomeHolderView holder, int position) {

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

    class HomeHolderView extends RecyclerView.ViewHolder {
        public HomeHolderView(View itemView) {
            super(itemView);
        }
    }
}

從基礎(chǔ)使用上看猪腕,我們明顯可以看出冗澈,RecyclerView 相比 ListView 在基礎(chǔ)使用上的區(qū)別主要有如下幾點:
ViewHolder 的編寫規(guī)范化了
RecyclerView 復(fù)用 Item 的工作 Google 全幫你搞定,不再需要像 ListView 那樣自己調(diào)用 setTag
RecyclerView 需要多出一步 LayoutManager 的設(shè)置工作.

布局效果

在最開始就提到陋葡,RecyclerView 能夠支持各種各樣的布局效果亚亲,這是 ListView 所不具有的功能,那么這個功能如何實現(xiàn)的呢腐缤?其核心關(guān)鍵在于 RecyclerView.LayoutManager 類中捌归。從前面的基礎(chǔ)使用可以看到,RecyclerView 在使用過程中要比 ListView 多一個 setLayoutManager 步驟柴梆,這個 LayoutManager 就是用于控制我們 RecyclerView 最終的展示效果的陨溅。


image.png

而 LayoutManager 只是一個抽象類而已,系統(tǒng)已經(jīng)為我們提供了三個相關(guān)的實現(xiàn)類 LinearLayoutManager(線性布局效果)绍在、GridLayoutManager(網(wǎng)格布局效果)门扇、StaggeredGridLayoutManager(瀑布流布局效果)。如果你想用 RecyclerView 來實現(xiàn)自己 YY 出來的一種效果偿渡,則應(yīng)該去繼承實現(xiàn)自己的 LayoutManager臼寄,并重寫相應(yīng)的方法,而不應(yīng)該想著去改寫 RecyclerView溜宽。關(guān)于 LayoutManager 的使用有下面一些常見的 API(有些在 LayoutManager 實現(xiàn)的子類中)
API 可以查看官方文檔吉拳,通常你想用 RecyclerView 實現(xiàn)某種效果,例如指定滾動到某個 Item 位置适揉,但是你在 RecyclerView 中又找不到可以調(diào)用的 API 時留攒,就可以跑到 LayoutManager 的文檔去看看,基本都在那里嫉嘀。另外還有一點關(guān)于瀑布流布局效果 StaggeredGridLayoutManager 想說的炼邀,看到網(wǎng)上有些文章寫的示例代碼,在設(shè)置了 StaggeredGridLayoutManager 后仍要去 Adapter 中動態(tài)設(shè)置 View 的高度剪侮,才能實現(xiàn)瀑布流拭宁,這種做法是完全錯誤的,之所以 StaggeredGridLayoutManager 的瀑布流效果出不來瓣俯,基本是 item 布局的 xml 問題以及數(shù)據(jù)問題導(dǎo)致杰标。如果要在 Adapter 中設(shè)置 View 的高度,則完全違背了 LayoutManager 的設(shè)計理念了彩匕。

HeaderView 和 FooterView

在 ListView 的設(shè)計中腔剂,存在著 HeaderView 和 FooterView 兩種類型的視圖,并且系統(tǒng)也提供了相應(yīng)的 API 來讓我們設(shè)置


image

使用 HeaderView 和 FooterView 的好處在于推掸,當(dāng)我們指向在 ListView 的頭部或者底部添加一個 View 的時候(例如:添加一個下拉刷新視圖桶蝎,底部加載更多視圖)驻仅,我們可以不用影響到 Adapter 的編寫谅畅,使用起來相當(dāng)方便登渣。而到了 RecyclerView 中,翻來翻去你都不會看到類似 addFooterView 毡泻、 addFooterView 這種 API胜茧,是的,沒錯仇味,壓根就沒有…這也是 RecyclerView 讓我覺得很雞肋的地方呻顽,按道理說應(yīng)該是使用頻率很高的 API,居然都不給我(一臉懵逼)丹墨。那有木有解決方法呢廊遍,肯定有,系統(tǒng)不給就自己動手豐衣足食唄贩挣。我想到的方法比較笨喉前,就是在 Adapter 中提供三種類型(Header,F(xiàn)ooter以及普通Item)的 Type 和 View王财,但是這種方法寫起來很麻煩卵迂,對 Adapter 的影響很大,改動的代碼量多并且也容易產(chǎn)生BUG绒净。這里需要吹一下鴻洋老師的解決方案了见咒,大家可以看他的文章:優(yōu)雅的為RecyclerView添加HeaderView和FooterView 。他的實現(xiàn)思路是通過裝飾者模式來擴(kuò)充 Adapter 的功能挂疆,從而實現(xiàn)添加 HeaderView 和 FooterView改览,并且不影響 Adapter 的編寫工作,牛逼的是還能支持多個 HeaderView 和 FooterView (雖然我暫時想不到有什么應(yīng)用場景缤言,哈哈宝当,不過先記著,以后說不定有用)墨闲。這是我目前看到的最贊成的方案了今妄,如果你有更 nice 的方案,也歡迎給我留言鸳碧。

局部刷新

在 ListView 中盾鳞,說到刷新很多童鞋會記得 notifyDataSetChanged() ,但是說到局部刷新估計有很多童鞋就知道得比較少了瞻离。我們知道在更新了 ListView 的數(shù)據(jù)源后腾仅,需要通過 Adapter 的 notifyDataSetChanged 來通知視圖更新變化,這樣做比較的好處就是調(diào)用簡單套利,壞處就是它會重繪每個 Item推励,但實際上并不是每個 Item 都需要重繪鹤耍。最常見的,例如:朋友圈點贊验辞,點贊只是更新當(dāng)前點贊的Item稿黄,并不需要每個 Item 都更新。然而 ListView 并沒有提供局部刷新刷新某個 Item 的 API 給我們跌造,同樣自己自足杆怕,套路大致如下方的 updateItemView:

image

動畫效果

如果你細(xì)心觀察上面 ListView 和 RecyclerView 局部更新 Item 的效果,你會發(fā)現(xiàn)相比 ListView 而言壳贪, RecyclerView 在做局部刷新的時候有一個漸變的動畫效果陵珍。這也是 RecyclerView 相對非常值得一提的地方,作為 ListView 自身并沒有為我們提供封裝好的 API 來實現(xiàn)動畫效果切換违施。所以互纯,如果要給 ListView 的 Item 加動畫,我們只能自己通過屬性動畫來操作 Item 的視圖磕蒲。 Github 也有很多封裝得好好的開源庫給我們用留潦,如:ListViewAnimations 就封裝了大量的效果供我們使用。
ListViewAnimations 主要大致實現(xiàn)方式是通過裝飾者模式來擴(kuò)充 Adapter 亿卤,并結(jié)合屬性動畫 Animator 來添加動畫效果愤兵。相比之下,RecyclerView 則為我們提供了很多基本的動畫 API 排吴,如下方的增刪移改

image

也可以通過相應(yīng)接口實現(xiàn)自己的動畫效果秆乳,方式也非常簡單,繼承 RecyclerView.ItemAnimator 類钻哩,并實現(xiàn)相應(yīng)的方法屹堰,再調(diào)用 RecyclerView 的 setItemAnimator(RecyclerView.ItemAnimator animator) 方法設(shè)置完即可實現(xiàn)自定義的動畫效果。
image

系統(tǒng)也為我們提供了兩個默認(rèn)的動畫實現(xiàn):SimpleItemAnimator 和 DefaultItemAnimator街氢。而 RecyclerView 在不手動調(diào)用 setItemAnimator 的情況下扯键,則默認(rèn)用了內(nèi)置的 DefaultItemAnimator 。


image

當(dāng)然編寫自定義的 ItemAnimator 也是需要一定工作量的珊肃,這里同樣為大家介紹一個針對 RecyclerView 開源的動畫庫:recyclerview-animators荣刑。其內(nèi)部封裝了大量的動畫效果給供我們調(diào)用。

那就是 ItemTouchHelper 伦乔。


image

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

  • 創(chuàng)建 ItemTouchHelper 實例烈和,同時實現(xiàn) ItemTouchHelper.SimpleCallback 中的抽象方法爱只,用于初始化 ItemTouchHelper

  • 調(diào)用 ItemTouchHelper 的 attachToRecyclerView 方法關(guān)聯(lián)上 RecyclerView 即可

示例代碼大致如下:

image

雖然代碼中有注釋,但還是稍稍解釋一下招刹,主要重寫的是 getMovementFlags 恬试、 onMove 窝趣、 onSwiped 三個抽象方法,getMovementFlags 用于告訴系統(tǒng)训柴,我們的 RecyclerView 到底是支持滑動還是拖曳哑舒。如上面的示例代碼,就是表示著同時支持上下左右四個方向的拖曳和左右兩個方向的滑動效果畦粮。如果時滑動散址,則 onSwiped 會被回調(diào)乖阵,如果是拖曳 onMove 會被回調(diào)宣赔。

監(jiān)聽 Item 的事件

ListView 為我們準(zhǔn)備了幾個專門用于監(jiān)聽 Item 的回調(diào)接口,如單擊瞪浸、長按儒将、選中某個 Item 等


image

說實話,其實我并不大喜歡這樣的設(shè)計对蒲,如 setOnItemClickListener 钩蚊,在我們不添加 HeaderView 和 FooterView 的時候,我們可以通過回調(diào)參數(shù)中的 position 去拿到數(shù)據(jù)源列表中對應(yīng) Item 的數(shù)據(jù)蹈矮。


image

但是砰逻,添加了 HeaderView 和 FooterView 之后就不一樣了,ListView 會把 HeaderView 和 FooterView 算入 position 內(nèi)泛鸟。假設(shè)你原先在 onItemClick 回調(diào)方法中寫了 mDataList.get(position) 這樣的業(yè)務(wù)代碼并且這段代碼運行良好許久蝠咆,但在某天你突然加了個 HeaderView 后,這段代碼就開始變的有問題了北滥,此時因為 HeaderView 占用的位置算入了 position 之內(nèi)刚操,所以 position 的最大值實際上是大于 mDataList 包含元素的個數(shù)值的,因此代碼會報數(shù)組越界的錯誤再芋。當(dāng)然菊霜,我們可以去避免這種問題的發(fā)生,就是不通過 position 來獲取數(shù)據(jù)济赎,二是通過回調(diào)方法中的 id 鉴逞。


image

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


image

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

而再來看看 RecyclerView 啥辨,它并沒有像 ListView 提供太多關(guān)于 Item 的某種事件監(jiān)聽涡匀,唯一的就是 addOnItemTouchListener


image

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

OK贯卦,關(guān)于 RecyclerView 和 ListView 一些常用的功能和 API 的對比,就大致到此焙贷。最后再來談?wù)?Android L 開始之后撵割,對 RecyclerView 和 ListView 的使用存在什么影響。

嵌套滾動機(jī)制

熟悉 Android 觸摸事件分發(fā)機(jī)制的童鞋肯定知道辙芍,Touch 事件在進(jìn)行分發(fā)的時候啡彬,由父 View 向它的子 View 傳遞,一旦某個子 View 開始接收進(jìn)行處理沸手,那么接下來所有事件都將由這個 View 來進(jìn)行處理外遇,它的 ViewGroup 將不會再接收到這些事件,直到下一次手指按下契吉。而嵌套滾動機(jī)制(NestedScrolling)就是為了彌補(bǔ)這一機(jī)制的不足跳仿,為了讓子 View 能和父 View 同時處理一個 Touch 事件。關(guān)于嵌套滾動機(jī)制(NestedScrolling)捐晶,實現(xiàn)上相對是比較復(fù)雜的菲语,此處就不去拓展說明,其關(guān)鍵在于 NestedScrollingChildNestedScrollingParent兩個接口惑灵,以及系統(tǒng)對這兩個接口的實現(xiàn)類 NestedScrollingChildHelperNestedScrollingParentHelper 大家可以查閱相關(guān)的資料山上。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市英支,隨后出現(xiàn)的幾起案子佩憾,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妄帘,死亡現(xiàn)場離奇詭異楞黄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)抡驼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門鬼廓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人致盟,你說我怎么就攤上這事碎税。” “怎么了馏锡?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵雷蹂,是天一觀的道長。 經(jīng)常有香客問我眷篇,道長萎河,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任蕉饼,我火速辦了婚禮,結(jié)果婚禮上玛歌,老公的妹妹穿的比我還像新娘昧港。我一直安慰自己,他們只是感情好支子,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布创肥。 她就那樣靜靜地躺著,像睡著了一般值朋。 火紅的嫁衣襯著肌膚如雪叹侄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天昨登,我揣著相機(jī)與錄音趾代,去河邊找鬼。 笑死丰辣,一個胖子當(dāng)著我的面吹牛撒强,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播笙什,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼飘哨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了琐凭?” 一聲冷哼從身側(cè)響起芽隆,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后胚吁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體臼闻,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年囤采,在試婚紗的時候發(fā)現(xiàn)自己被綠了述呐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蕉毯,死狀恐怖乓搬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情代虾,我是刑警寧澤进肯,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站棉磨,受9級特大地震影響江掩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乘瓤,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一环形、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧衙傀,春花似錦抬吟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至聪建,卻和暖如春钙畔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背金麸。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工擎析, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钱骂。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓叔锐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親见秽。 傳聞我的和親對象是個殘疾皇子愉烙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容