優化 listview 有哪些方法?
為了避免大家誤會這個回答只是老生常談 ListView 的重用機制,編輯一下。
我這裡說一下我用 ListView 的一些經驗,為了盡量說的全面一些,這裡列一些 Tips,具體的代碼可以找相關的文章,或者一起交流:- 首先,雖然大家都知道,還是提一下,利用好 convertView 來重用 View,切忌每次 getView() 都新建。ListView 的核心原理就是重用 View。ListView 中有一個回收器,Item 滑出界面的時候 View 會回收到這裡,需要顯示新的 Item 的時候,就盡量重用回收器裡面的 View。
- 利用好 View Type,例如你的 ListView 中有幾個類型的 Item,需要給每個類型創建不同的 View,這樣有利於 ListView 的回收,當然類型不能太多;
- 盡量讓 ItemView 的 Layout 層次結構簡單,這是所有 Layout 都必須遵循的;
- 善用自定義 View,自定義 View 可以有效的減小 Layout 的層級,而且對繪製過程可以很好的控制;
- 盡量能保證 Adapter 的 hasStableIds() 返回 true,這樣在 notifyDataSetChanged() 的時候,如果 id 不變,ListView 將不會重新繪製這個 View,達到優化的目的;
- 每個 Item 不能太高,特別是不要超過屏幕的高度,可以參考 Facebook 的優化方法,把特別複雜的 Item 分解成若干小的 Item,特別推薦看一下這個文章:https://code.facebook.com/posts/879498888759525/fast-rendering-news-feed-on-android/
- 為了保證 ListView 滑動的流暢性,getView() 中要做盡量少的事情,不要有耗時的操作。特別是滑動的時候不要載入圖片,停下來再載入,這個庫可以幫助你 Glide:https://github.com/bumptech/glide
- 使用 RecycleView 代替。 ListView 每次更新數據都要 notifyDataSetChanged(),有些太暴力了。RecycleView 在性能和可定製性上都有很大的改善,推薦使用。
- 有時候,需要從根本上考慮,是否真的要使用 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;
}
用RecyclerView
-------------------
發現一個問題,如果要某個item動態改變,滾動的時候會出錯,大概也是回收機制的問題。
例如:限制item內TextView行數為5行,超過五行後添加展開按鈕,滾下去展開一個,再滾回來,看圖吧。
剛好今天翻譯了一篇國外技術文章:
大家可以參考下,發表在我的個人博客中
提升Android ListView性能的幾個技巧只知道一種方式
重用convertView,效果很明顯。
http://stackoverflow.com/questions/1320478/how-to-load-the-listview-smoothly-in-androidViewHolder 這種方法來自於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.使用ViewHolder3.在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 無限自啟的黑科技究竟是怎麼做到的?