優化 listview 有哪些方法?


為了避免大家誤會這個回答只是老生常談 ListView 的重用機制,編輯一下。

我這裡說一下我用 ListView 的一些經驗,為了盡量說的全面一些,這裡列一些 Tips,具體的代碼可以找相關的文章,或者一起交流:

  1. 首先,雖然大家都知道,還是提一下,利用好 convertView 來重用 View,切忌每次 getView() 都新建。ListView 的核心原理就是重用 View。ListView 中有一個回收器,Item 滑出界面的時候 View 會回收到這裡,需要顯示新的 Item 的時候,就盡量重用回收器裡面的 View。
  2. 利用好 View Type,例如你的 ListView 中有幾個類型的 Item,需要給每個類型創建不同的 View,這樣有利於 ListView 的回收,當然類型不能太多;
  3. 盡量讓 ItemView 的 Layout 層次結構簡單,這是所有 Layout 都必須遵循的;
  4. 善用自定義 View,自定義 View 可以有效的減小 Layout 的層級,而且對繪製過程可以很好的控制;
  5. 盡量能保證 Adapter 的 hasStableIds() 返回 true,這樣在 notifyDataSetChanged() 的時候,如果 id 不變,ListView 將不會重新繪製這個 View,達到優化的目的;
  6. 每個 Item 不能太高,特別是不要超過屏幕的高度,可以參考 Facebook 的優化方法,把特別複雜的 Item 分解成若干小的 Item,特別推薦看一下這個文章:https://code.facebook.com/posts/879498888759525/fast-rendering-news-feed-on-android/
  7. 為了保證 ListView 滑動的流暢性,getView() 中要做盡量少的事情,不要有耗時的操作。特別是滑動的時候不要載入圖片,停下來再載入,這個庫可以幫助你 Glide:https://github.com/bumptech/glide
  8. 使用 RecycleView 代替。 ListView 每次更新數據都要 notifyDataSetChanged(),有些太暴力了。RecycleView 在性能和可定製性上都有很大的改善,推薦使用。
  9. 有時候,需要從根本上考慮,是否真的要使用 ListView 來實現你的需求,或者是否有其他選擇?

寫的有點散,有些也是相互穿插的。這裡面提到的都是一些原則,詳細的解決方案,每個都能在網上找到相關的文章。


有一個小細節,很多開發人員都沒有注意過

比如你的Item中有三個按鈕,你要為三個按鈕分別定義點擊事件,如何定義?

也許你會在getView中這樣做

button1.setOnclickListener(new View.OnClickListener() {
@override
public void onClick(View v) {
//balabalabala...
}
});

button2.setOnclickListener(new View.OnClickListener() {
@override
public void onClick(View v) {
//balabalabala...
}
});

button3.setOnclickListener(new View.OnClickListener() {
@override
public void onClick(View v) {
//balabalabala...
}
});

如果你每屏顯示7個Item,你一共創建了21個listener對象在內存中,如果View回收不暢,會更多,這樣,在滾動的時候頻繁GC 就會導致卡頓(這裡描述有誤,請看7月25日更新)

如果你在Adapter初始化的時候創建一個Listener

public MyAdapter () {
myListener = new View.OnClickListener() {
@override
public void onClick(View v) {
v.getTag()
v.getId()
//balabalabala...
}
});
}

通過傳入的View v這個參數判斷是哪一個button被點擊,這樣,無論View如何創建,你只創建了1個Listener對象

這只是一個小細節,優化的方式要綜合使用,才會事半功倍

------------------7月24日更-----------------

v.getTag() 這個tag本不是用來存數據的,通俗點講它和view 的Id是同一個東西,只不過tag的類型是Object。實際上在tag中存儲數據是不符合規範的方式

但其實View類有兩種tag,

setTag(Object tag) 方法將tag保存在一個成員變數中,findViewWithTag正是遍歷此tag

setTag(int key, Object tag) 方法是最終是調用View類中的setKeyedTag(int key, Object tag)

這是一個私有方法

他是用 SparseArray 實現的,我們可以把需要的東西存到這裡面(其實觀察源碼可以發現,系統很多時候都是這樣做的)

這裡要注意一點,參數key必須是唯一的,那麼我們可以這樣做

那麼需要先在res/values/strings.xml中添加

&
&&
&&
&

使用的時候寫成

view.setTag(R.id.tag_first, obj1);
view.setTag(R.id.tag_second, obj2);

-------------------------關於如何綁定數據的問題-----------------

有人問,如果這樣寫,所有button只能通過id區分邏輯,無法傳入每個item的數據

我們可以將數據通過view 的tag帶進來

public View getView(.....) {

....

v.setTag(key, getItem(position));

....

}

然後在listener中通過v.getTag()將數據取出

-----------------------7月25日update-------------------

這裡我有一個失誤

如果listener裡面的邏輯與當前的item有關,那麼其實並不只是創建了21個listener對象

public void getView (View convertView ,final int position ....) {
if (convertView == null) {
View v = LayoutInflater.from(mContext).inflate(...);
v.setOnclickListener(new View.OnClickListener () {
@override
public void onClick(View v) {
getItem(position);
}
});
} else {

}
}

你看下,如果綁定數據在convertView為空的情況下確實只創建了有限個listener,

但是在這種情況下綁定上的數據只有View創建時的7個,之後不為空的情況下沒有更換listener導致重用的view數據是新的,listener裡面的position依然是過去創建view時的7個之一,不會變化(注意參數上的final)

若在else裡面再重新setListener,view是有重用,listener被換成新的,並與新的position綁定,老的listener就會變成一個廢對象,等待gc回收,隨著list滾動,越來越多

關鍵是我們的業務與position這個參數有關

不知道這回我表述清楚了沒有


1.重用 convertView

用以避免重複創建 View,重複創建 View 代價較大,而且如果重用 view 不改變寬高,重用View可以減少重新分配緩存造成的內存頻繁分配/回收;

2. 避免在 getView 中有 重複調用的 findViewById

findViewById 的實現是遍歷,如果你定義的 View 越複雜代價越大。

Google 推薦的做法是用 ViewHolder,然後保存在 view 的 tag 中。現在 RecyclerView 也是強制使用 ViewHolder 了。

3. 設置 View (如 TextView#setText )之前先對比數據是否有改變

一般來說,【比較兩個數據的代價】遠小於【 View 的重繪的代價】

4. 避免在 getView 函數中直接載入 Image 或做其他比較耗時的操作

載入本地 Image 需要載入內存以及解析 Bitmap ,都是比較耗時的操作。

用戶快速滑動列表時,會大量調用 getView ,而 getView 是在主線程中被調用的。如果你在 getView 函數中直接載入 Image 或做其他耗時操作,就會造成滑動比較卡。載入 ImageView 的解決方案就是開一個線程去把做這事。有很多第三庫可以做這事。

5. ListView 中元素避免半透明

半透明繪製需要大量乘法計算,在滑動時不停重繪會造成大量的計算,在比較差的機子上會比較卡。在設計上能不半透明就不不半透明。實在要弄的話我個人是用個比較偷懶的方法,是在滑動的時候把半透明設置成不透明,滑動完再重新設置成半透明。

6. 盡量開啟硬體加速

硬體加速提升巨大,避免使用一些不支持的函數導致含淚關閉某個地方的硬體加速。

當然這一條不只是對 ListView。

. 用 ListView 威力加強版 -- RecyclerView

更多的新武將,更多的姿勢,更規範的使用,更好用的動畫,更加強大的變化


@SamuraiSong的回答中提到的多個item點擊的實現,還可以直接ViewHolder聲明onClick介面,然後根據view的id來區分不同item。

如果需要傳遞數據,可以傳遞model或者position到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
}
}
}

public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = inflater.inflate(R.layout.list_item, parent, false);
holder = new ViewHolder();
holder.name = (TextView) convertView.findViewById(R.id.name);
holder.name.setOnClickListener(holder);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//設置holder
holder.name.setText(list.get(position).partname);
//設置position
holder.setPosition(position);
return convertView;
}

格式不好,可以查看blog~

ListView與BaseAdapter優化 // Jianqiu"s blog


用RecyclerView

-------------------

發現一個問題,如果要某個item動態改變,滾動的時候會出錯,大概也是回收機制的問題。

例如:限制item內TextView行數為5行,超過五行後添加展開按鈕,滾下去展開一個,再滾回來,看圖吧。


剛好今天翻譯了一篇國外技術文章:

大家可以參考下,發表在我的個人博客中

提升Android ListView性能的幾個技巧


只知道一種方式

重用convertView,效果很明顯。

http://stackoverflow.com/questions/1320478/how-to-load-the-listview-smoothly-in-android


ViewHolder 這種方法來自於google i/o上的一種投機取巧的用法。這種方法是有負面效果的,就是會照成view布局錯位,比如拖動時縮小的情況。

tag這種東西顧名思義是用來做標記的,而且也有findViewByTag方法,明顯不是用來存儲數據的。

對於一般的列表直接判斷convertView==null,然後View.inflate出來,再findViewById就可以了,比較複雜的列表,可以extends Layout把各種view保存到成員變數裡面,使用的時候類型轉換就可以了。

ListView的優化,主要就是convertView的重複利用,而view的構造主要在於載入各種子view的耗時上。


我做的私活全是直接return一個view的。。。

感覺我好沒有節操啊


原來看到過一個終極版的listview優化,viewholder中找控制項用的泛型,親測能用且效果很好,具體地址忘記了,不過現在有RecycleView可以用來代替Listview了,可以試試


這麼多回答,加在一起已經非常詳細了,我來總結一下:

  • 復用convertView對象,減小內存壓力
  • 使用ViewHolder,減少findViewById次數
  • 使用圖片緩存、壓縮圖片、非同步載入圖片
  • listview滑動的時候,不載入圖片,讓滑動更加流暢
  • 只載入可見item的圖片


google io上講過這個問題

1.使用convertView

2.使用viewHolder

http://files.cnblogs.com/over140/2011/3/Th_0230_TurboChargeYourUI-HowtomakeyourAndroidUIfastandefficient.pdf


通過代碼布局避免inflate帶來的消耗,滑動的時候停止子線程,減少布局的嵌套,可以參考新浪微博和qq空間大量使用自定義view 比如九宮格圖片其實就是一個view


只能提個方向,

根據樓主的簡單描述,「優化」一詞簡單認定為性能優化吧,以FPS為指標,可以從這麼幾點去考慮:

1. 界面是否過渡繪製

2. 布局是否合理

3. 對象是否循環可利用,即最少的new操作

4. UI線程是否不被阻塞 io與ui分配,甚至new操作與ui線程分離

而以上每點,都不是回個帖就能說得明白的...所以我編不下去了,樓主保重。


充分利用已生成的View對象,減少系統生成的view個數,並充分利用view對象的TAG,避免系統大量生成一次性使用的數據。

繪製ListView之前,將會先調用getView方法來獲取Item的個數。之後每繪製一個 Item就會調用一次getView方法,可以引用事先定義好的xml來確定顯示的效果並返回一個View對象作為一個Item顯示出來。也正是在這個過程中完成了適配器的主要轉換功能,把數據以開發者想要的效果顯示出來。通過getView的重複調用,可以使得ListView的使用更為簡單和靈活。


修改getView 使用 ViewHolder 模式


1.復用convertView

2.使用ViewHolder

3.在VIewHolder 使用關鍵字 static

or 直接用RecycleView


復用convertview和用viewholder,是最常見的


代碼層面的,少新建對象;

viewholder復用;

多數據數據bean的處理等~


getView中復用convertView和ViewHolder聯合使用,推薦使用


RecyclerView


推薦閱讀:

請幫忙推薦適合中老年人使用的智能手機?
推一本以Android Studio為開發環境的安卓開發教學書籍吧?
yun os到底是不是Android?
iOS、Android、webOS、Windows Phone 7、BlackBerry OS 哪一個使用體驗好一點?
Android 上 App 無限自啟的黑科技究竟是怎麼做到的?

TAG:Android開發 | Android |