ListView/RecyclerView相關(guān)知識點(diǎn)

一氧腰、緩存機(jī)制對比

1.1 ListView(兩級緩存)

ListView
是否需要回調(diào)createView 是否需要回調(diào)bindView 生命周期 備注
mActiveViews onLayout函數(shù)周期中 用于屏幕內(nèi)ItemView快速重用
mScrapViews 與mAdapter一致,當(dāng)adapter被更換時(shí)闺金,mScrapViews會被清空

1.2 RecyclerView(四級緩存)

RecyclerView
是否需要回調(diào)createView 是否需要回調(diào)bindView 生命周期 備注
mAttachedScrap onLayout函數(shù)周期內(nèi) 用于屏幕內(nèi)ItemView快速重用
mCacheViews 與Adapter一致县忌,當(dāng)mAdapter被更換時(shí)鳞骤,mCacheViews即被緩存至mRecyclerPool 默認(rèn)上限為2,即緩存屏幕外2個(gè)ItemView
mViewCacheExtension 不直接使用词裤,需要用戶在定制刺洒,默認(rèn)不實(shí)現(xiàn)
mRecyclerPool 與自身生命周期一致,不再被引用時(shí)即被釋放 默認(rèn)上限為5吼砂,技術(shù)上可以實(shí)現(xiàn)所有RecyclerViewPool共用同一個(gè)

1.3 ListView和RecyclerView獲取緩存流程

ListView獲取緩存原理

listview獲取緩存原理.jpg

RecyclerView獲取緩存原理

recyclerview獲取緩存原理.jpg

二逆航、RecyclerView局部刷新

ListView離屏緩存實(shí)現(xiàn)機(jī)制:ListView從mScrapViews根據(jù)pos獲取相應(yīng)的緩存,但是沒有直接用渔肩,而是重新getView纸泡,bindView。RecyclerView中通過pos獲取的是viewholder赖瞒,即pos->(view, viewHolder, flag)女揭,標(biāo)志位flag的作用是判斷View是否需要重新bindView。

三栏饮、ListView開發(fā)注意事項(xiàng)

3.1 ListView加載圖片數(shù)據(jù)混亂

ListView是根據(jù)RecycleBin來實(shí)現(xiàn)ItemView的復(fù)用的吧兔,出現(xiàn)圖片數(shù)據(jù)錯(cuò)亂加載的原因是每次從mScrapViews中獲取ItemView,都會重新調(diào)用getView()方法袍嬉,但是圖片是異步加載的倦炒,但是ItemView是復(fù)用的幢竹。所以可以通過tag來區(qū)分當(dāng)前ImageView和要顯示的圖片是否一致,如果一致則不去網(wǎng)絡(luò)加載。

// 給 ImageView 設(shè)置一個(gè) tag
holder.img.setTag(imgUrl);
// 預(yù)設(shè)一個(gè)圖片
holder.img.setImageResource(R.drawable.ic_launcher);

// 通過 tag 來防止圖片錯(cuò)位
if (imageView.getTag() != null && imageView.getTag().equals(imageUrl)) {
    imageView.setImageBitmap(result);
}
231543494598347.jpg

3.2 ListView的優(yōu)化

  • 使用ViewHolder復(fù)用機(jī)制

由于ListView的實(shí)現(xiàn)機(jī)制劫笙,每次從緩存里面獲取ItemView都需要getView()推捐,重新inflate布局比較耗時(shí)梳庆,可以給ListView添加ViewHolder并設(shè)置tag贯吓,代碼如下:

ViewHolder viewHolder = null;
View view = null;//getView方法要返回的View
if(convertView == null){//如果當(dāng)前沒有可以復(fù)用的View
   viewHolder = new ViewHolder();
   view = LayoutInflater.from(context).inflate(resourceId,null);//那么就從XML文件生成一個(gè)View
   viewHolder.resourceViewName = view.findViewById(resouceViewId);//從XML中找到對應(yīng)的View
   view.setTag(viewHolder);//將ViewHolder設(shè)置在當(dāng)前ItemView的tag里面
}else{//否則
   view = convertView;//就使用可以復(fù)用的View
   viewHolder = (ViewHolder)convertView.getTag();//從復(fù)用的View中取出viewHoder
}

class  ViewHolder {  
    TextView name;  
}  
  • 對圖片的優(yōu)化

    給圖片設(shè)置內(nèi)存緩存(LruCache,SoftReference)弓柱,當(dāng)使用圖片時(shí)可用直接加載好的圖片沟堡,這樣體驗(yàn)會更流暢侧但;另外當(dāng)用戶快速滑動ListView時(shí)候,可以默認(rèn)為不加載航罗。

listView.setOnScrollListener(new AbsListView.OnScrollListener() {
  @Override
  public void onScrollStateChanged(AbsListView view, int scrollState) {
       if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {//list停止?jié)L動時(shí)加載圖片    
           loadImage(startPos, endPos);// 異步加載圖片   ,只加載可以看到的圖片    
       }    
  }

  @Override
  public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  //設(shè)置當(dāng)前屏幕顯示的起始pos和結(jié)束pos   
        startPos = firstVisibleItem;    
        endPos = firstVisibleItem + visibleItemCount;    
        if (endPos >= totalItemCount) {    
         endPos = totalItemCount - 1;    
        }    
  }
});
  • onClickListener的處理

當(dāng)contentView中有很多View需要對點(diǎn)擊事件的處理禀横,可以集中在viewHolder中處理:

class  ViewHolder implements OnClickListener{  
    int position;  
    TextView name;  
  
    public void setPosition(int position){  
        this.position = position;  
    }  
  
    @Override  
    public void onClick(View v) {  
        switch (v.getId()){  
            //XXXX  
        }  
    }  
}  

ViewHolder viewHolder = null;
View view = null;//getView方法要返回的View
if(convertView == null){//如果當(dāng)前沒有可以復(fù)用的View
   viewHolder = new ViewHolder();
   view = LayoutInflater.from(context).inflate(resourceId,null);//那么就從XML文件生成一個(gè)View
   viewHolder.resourceViewName = view.findViewById(resouceViewId);//從XML中找到對應(yīng)的View
   viewHolder.setPosition(position);//設(shè)置位置
   viewHolder.name.setOnClickListener(viewHolder);//設(shè)置ClickListener
   view.setTag(viewHolder);//將ViewHolder設(shè)置在當(dāng)前ItemView的tag里面
}else{//否則
   view = convertView;//就使用可以復(fù)用的View
   viewHolder = (ViewHolder)convertView.getTag();//從復(fù)用的View中取出viewHoder
}

四、RecyclerView開發(fā)注意事項(xiàng)

4.1 RecyclerView的性能優(yōu)化

  • 數(shù)據(jù)方面的處理

    類似于這種對數(shù)據(jù)進(jìn)行html柵格化的操作 ( Html.fromHtml(data) )粥血, 有必要放在子線程和網(wǎng)絡(luò)加載中一起處理柏锄,可以讓用戶覺得數(shù)據(jù)加載的時(shí)間會稍微長點(diǎn);分頁加載的數(shù)據(jù)要進(jìn)行緩存起來复亏,加快加載速度趾娃。

  • 布局優(yōu)化

    1. 減少過度繪制,可考慮自定義View來減少層級蜓耻,或更合理的設(shè)置布局來減少層級,不推薦在RecyclerView中使用ConstranintLayout械巡。
    2. 減少xml文件inflate時(shí)間刹淌,如果當(dāng)item的復(fù)用幾率很低的情況下,隨著type增多讥耗,可以嘗試用代碼去生成布局有勾,這樣可以加快布局的加載速度。
    3. 減少View對象的創(chuàng)建古程,盡量的簡化ItemView蔼卡,設(shè)計(jì)ItemType時(shí),多對ViewType能夠共用的地方設(shè)計(jì)成自定義View挣磨。
  • 其他

    1. 25.1.0版本及以上使用Prefetch功能雇逞,RecyclerView視圖數(shù)據(jù)預(yù)取。

    在RecyclerView顯示一幀的過程包括UI線程創(chuàng)建視圖和綁定視圖的操作茁裙,然后交給RenderThread發(fā)指令通知GPU進(jìn)行渲染的操作塘砸,但是在顯示一幀完成和下幀開始創(chuàng)建視圖的過程中,UI線程是無所事事的晤锥,可以在這個(gè)時(shí)候?qū)竺娴囊晥D進(jìn)行創(chuàng)建和綁定操作掉蔬。

    1. 如果Item高度是固定的話,可以使用RecyclerView.setHasFixedSize(true)矾瘾;來避免requestLayout浪費(fèi)資源女轿。

    2. 設(shè)置RecyclerView.addOnScrollListener(listener),來對滑動過程中停止加載的操作壕翩。

    3. 如果不要求動畫蛉迹,可以調(diào)用 getItemAnimator().setSupportsChangeAnimations(false),把默認(rèn)動畫關(guān)閉放妈。

    4. 對TextView使用String.toUpperCase來替代 android:textAllCaps="true"婿禽。

    5. 通過重寫RecyclerView.onViewRecycled(holder)來回收資源赏僧,當(dāng)這個(gè)方法被回調(diào)的時(shí)候,就表示表示這個(gè)Holder已經(jīng)被扔進(jìn)mRecyclerPool.mScrap里了扭倾,也就是再次取出的時(shí)候會經(jīng)過onBindViewHolder方法重新綁定數(shù)據(jù)淀零。

    6. 通過設(shè)置RecyclerView.setItemViewCacheSize(size);來加大RecyclerView的緩存膛壹,用空間來換取時(shí)間上的流暢性驾中。

    7. 如果多個(gè)RecyclerView的Adapter是一樣的,比如嵌套的RecyclerView中存在一樣的Adapter模聋,可以設(shè)置RecyclerView.setRecycledViewPool(pool)肩民;來共用一個(gè)RecycledViewPool。

    8. 對ItemView設(shè)置監(jiān)聽器链方,不要對每個(gè)Item都調(diào)用addXXListener持痰,應(yīng)該共用一個(gè)Listener,根據(jù)ID來進(jìn)行不同的操作祟蚀,優(yōu)化了對象的頻繁創(chuàng)建帶來的資源消耗工窍。

    9. 通過getExtraLayoutSpace來增加RecyclerView預(yù)留的額外空間。

    //顯示范圍之外提供更大的緩存空間前酿。
    new LinearLayoutManager(this) {
        @Override
        protected int getExtraLayoutSpace(RecyclerView.State state) {
            return size;
        }
    };
    

4.2 stableId 的使用

setHasStableIds用來標(biāo)識每一個(gè)itemView是否需要一個(gè)唯一標(biāo)識患雏,當(dāng)stableId設(shè)置為true的時(shí)候,每一個(gè)itemView數(shù)據(jù)就有一個(gè)唯一標(biāo)識罢维。getItemId()返回代表這個(gè)ViewHolder的唯一標(biāo)識淹仑,如果沒有設(shè)置stableId唯一性,返回NO_ID=-1肺孵。通過setHasStableIds可以使itemView的焦點(diǎn)固定匀借,從而解決RecyclerView的notify方法使得圖片加載時(shí)閃爍問題。注意:setHasStableIds()必須在 setAdapter() 方法之前調(diào)用平窘,否則會拋異常怀吻。因?yàn)镽ecyclerView.setAdapter后就設(shè)置了觀察者,設(shè)置了觀察者stateIds就不能變了初婆。

4.3 7.0新工具類DiffUtil

DiffUtil是support-v7:24.2.0中的新工具類蓬坡,它用來比較兩個(gè)數(shù)據(jù)集,尋找出舊數(shù)據(jù)集—>新數(shù)據(jù)集的最小變化量磅叛,它和mAdapter.notifyDataSetChanged()最大不同在于`它會自動計(jì)算新老數(shù)據(jù)集的差異屑咳,并根據(jù)差異情況,自動調(diào)用以下四個(gè)方法:

adapter.notifyItemRangeInserted(position, count);
adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);

且調(diào)用notifyDataSetChanged()不會觸發(fā)RecyclerView的動畫(刪除弊琴、新增兆龙、位移、change動畫),其次性能較低紫皇,它不管數(shù)據(jù)是否一樣都整個(gè)刷新了一遍整個(gè)RecyclerView 慰安,使用如下:

public abstract static class Callback {
    public abstract int getOldListSize();//老數(shù)據(jù)集size

    public abstract int getNewListSize();//新數(shù)據(jù)集size

    //新老數(shù)據(jù)集在同一個(gè)position的Item是否是一個(gè)對象,如果給itemView設(shè)置了stableIds聪铺,則僅比較它們單獨(dú)的           //id(可能內(nèi)容不同化焕,如果這里返回true,會調(diào)用下面的方法)
    public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);

    //這個(gè)方法僅僅是上面方法返回true才會調(diào)用铃剔,判斷item的內(nèi)容是否有變化撒桨,類似于Object.equals(Object)
    public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);

    //當(dāng)areItemsTheSame()返回true且areContentsTheSame()返回false,
    //同之處用下面的方法找出兩個(gè)itemView的data不        
    @Nullable
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        return null;
    }
}

//使用的時(shí)候?qū)崿F(xiàn)Callback接口
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new ProductListDiffCallback(mProducts, newProducts));
diffResult.dispatchUpdatesTo(mProductAdapter);

4.4 NestedScrollView嵌套RecyclerView

  • 滑動IRecyclerView列表會出現(xiàn)強(qiáng)烈的卡頓感

    mRecyclerView.setNestedScrollingEnabled(false);

    RecyclerView默認(rèn)是setNestedScrollingEnabled(true),是支持嵌套滾動的,也就是說當(dāng)它嵌套在NestedScrollView中時(shí),默認(rèn)會隨著NestedScrollView滾動而滾動,放棄了自己的滾動键兜。將該值置false可以讓RecyclerView不支持嵌套滑動凤类,這樣RecyclerView可以自己響應(yīng)滑動事件。

  • 每次打開界面都是定位在RecyclerView在屏幕頂端,列表上面的布局都被頂上去了

    RecyclerView搶占了焦點(diǎn),自動滾動導(dǎo)致的.

    RecyclerView會在構(gòu)造方法中調(diào)用setFocusableInTouchMode(true), 搶占焦點(diǎn)后一定會定位到第一行的位置普气,可以在NestedScrollView中添加屬性:android:focusableInTouchMode="true"谜疤,同時(shí)在RecyclerView中添加屬性:android:descendantFocusability="blocksDescendants"或直接設(shè)置mRecyclerVIew.setFocusableInTouchMode(false)

4.5 圖片混亂和閃爍的問題

  • 當(dāng)Item還在加載圖片的過程中,被移除屏幕可視范圍现诀,不需要繼續(xù)加載這張圖片了夷磕,可以在onViewRecycled中取消圖片的加載,這樣就不會造成圖片加載完設(shè)置到其他Item的ImageView中了赶盔。
  • 每一個(gè)經(jīng)過屏幕可視區(qū)域的item企锌,加載的圖片都要放到緩存中榆浓,即使item離開了可視區(qū)域于未,也要加載完畢并放入緩存中,方便下次瀏覽時(shí)能夠快速加載陡鹃,每次onBind時(shí)對ImageView設(shè)置tag標(biāo)記烘浦,如果tag標(biāo)記已經(jīng)被更改了,舊線程加載好的圖片不再設(shè)置到ImageView中萍鲸。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闷叉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子脊阴,更是在濱河造成了極大的恐慌握侧,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘿期,死亡現(xiàn)場離奇詭異品擎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)备徐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門萄传,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜜猾,你說我怎么就攤上這事秀菱≌裎埽” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵衍菱,是天一觀的道長赶么。 經(jīng)常有香客問我,道長梦碗,這世上最難降的妖魔是什么禽绪? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮洪规,結(jié)果婚禮上印屁,老公的妹妹穿的比我還像新娘。我一直安慰自己斩例,他們只是感情好雄人,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著念赶,像睡著了一般础钠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叉谜,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天旗吁,我揣著相機(jī)與錄音,去河邊找鬼停局。 笑死很钓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的董栽。 我是一名探鬼主播码倦,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锭碳!你這毒婦竟也來了袁稽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤擒抛,失蹤者是張志新(化名)和其女友劉穎推汽,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體歧沪,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡歹撒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了槽畔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栈妆。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鳞尔,到底是詐尸還是另有隱情嬉橙,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布寥假,位于F島的核電站市框,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏糕韧。R本人自食惡果不足惜枫振,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萤彩。 院中可真熱鬧粪滤,春花似錦、人聲如沸雀扶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愚墓。三九已至予权,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浪册,已是汗流浹背扫腺。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留村象,地道東北人笆环。 一個(gè)月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像煞肾,于是被迫代替她去往敵國和親咧织。 傳聞我的和親對象是個(gè)殘疾皇子嗓袱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355