一步一步實現listview載入的性能優化

来源:http://www.cnblogs.com/goagent/archive/2016/01/25/5158064.html
-Advertisement-
Play Games

listview載入的核心是其adapter,本文針對listview載入的性能優化就是對adpter的優化,總共分四個層次:0、最原始的載入1、利用convertView2、利用ViewHolder3、實現局部刷新[轉載請保留本文地址:http://www.cnblogs.com/goagent/...


listview載入的核心是其adapter,本文針對listview載入的性能優化就是對adpter的優化,總共分四個層次:

0、最原始的載入

1、利用convertView

2、利用ViewHolder

3、實現局部刷新

 [轉載請保留本文地址:http://www.cnblogs.com/goagent/p/5158064.html] 

〇、最原始的載入

這裡是不經任何優化的adapter,為了看起來方便,把listview的數據直接在構造函數里傳給adapter了,代碼如下:

 1     private class AdapterOptmL0 extends BaseAdapter {
 2         private LayoutInflater mLayoutInflater;
 3         private ArrayList<Integer> mListData;
 4         
 5         public AdapterOptmL0(Context context, ArrayList<Integer> data) {
 6             mLayoutInflater = LayoutInflater.from(context);
 7             mListData = data;
 8         }
 9         
10         @Override
11         public int getCount() {
12             return mListData == null ? 0 : mListData.size();
13         }
14 
15         @Override
16         public Object getItem(int position) {
17             return mListData == null ? 0 : mListData.get(position);
18         }
19 
20         @Override
21         public long getItemId(int position) {
22             return position;
23         }
24 
25         @Override
26         public View getView(int position, View convertView, ViewGroup parent) {
27             View viewRoot = mLayoutInflater.inflate(R.layout.listitem, parent, false);
28             if (viewRoot != null) {
29                 TextView txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
30                 txt.setText(getItem(position) + "");
31             }
32             return viewRoot;
33         }
34     }

  [轉載請保留本文地址:http://www.cnblogs.com/goagent/p/5158064.html] 

一、利用convertView

上述代碼的第27行在Eclipse中已經提示警告:

Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling

這個意思就是說,被移出可視區域的view是可以回收復用的,它作為getview的第二個參數已經傳進來了,所以沒必要每次都從xml里inflate。

經過優化後的代碼如下: 

 1     @Override
 2     public View getView(int position, View convertView, ViewGroup parent) {
 3         if (convertView == null) {
 4             convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
 5         }
 6         if (convertView != null) {
 7             TextView txt = (TextView)convertView.findViewById(R.id.listitem_txt);
 8             txt.setVisibility(View.VISIBLE);
 9             txt.setText(getItem(position) + "");
10         }
11         return convertView;
12     }

上述代碼加了判斷,如果傳入的convertView不為null,則直接復用,否則才會從xml里inflate。

按照上述代碼,如果手機一屏最多同時顯示5個listitem,則最多需要從xml里inflate 5 次,比AdapterOptmL0中每個listitem都需要inflate顯然效率高多了。

上述的用法雖然提高了效率,但帶來了一個陷阱如果復用convertView,則需要重置該view所有可能被修改過的屬性

舉個例子:

如果第一個view中的textview在getview中被設置成INVISIBLE了,而現在第一個view在滾動過程中出可視區域,並假設它作為參數傳入第十個view的getview而被覆用

那麼,在第十個view的getview裡面不僅要setText,還要重新setVisibility,因為這個被覆用的view當前處於INVISIBLE狀態!

[轉載請保留本文地址:http://www.cnblogs.com/goagent/p/5158064.html] 

二、利用ViewHolder

從AdapterOptmL0第27行的警告中,我們還可以看到編譯器推薦了一種模型叫ViewHolder,這是個什麼東西呢,先看代碼:

 1     private class AdapterOptmL2 extends BaseAdapter {
 2         private LayoutInflater mLayoutInflater;
 3         private ArrayList<Integer> mListData;
 4         
 5         public AdapterOptmL2(Context context, ArrayList<Integer> data) {
 6             mLayoutInflater = LayoutInflater.from(context);
 7             mListData = data;
 8         }
 9         
10         private class ViewHolder {
11             public ViewHolder(View viewRoot) {
12                 txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
13             }
14             public TextView txt;
15         }
16         
17         @Override
18         public int getCount() {
19             return mListData == null ? 0 : mListData.size();
20         }
21 
22         @Override
23         public Object getItem(int position) {
24             return mListData == null ? 0 : mListData.get(position);
25         }
26 
27         @Override
28         public long getItemId(int position) {
29             return position;
30         }
31 
32         @Override
33         public View getView(int position, View convertView, ViewGroup parent) {
34             if (convertView == null) {
35                 convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
36                 ViewHolder holder = new ViewHolder(convertView);
37                 convertView.setTag(holder);
38             }
39             if (convertView != null && convertView.getTag() instanceof ViewHolder) {
40                 ViewHolder holder = (ViewHolder)convertView.getTag();
41                 holder.txt.setVisibility(View.VISIBLE);
42                 holder.txt.setText(getItem(position) + "");
43             }
44             return convertView;
45         }
46     }

從代碼中可以看到,這一步做的優化是用一個類ViewHolder來保存listitem裡面所有找到的子控制項,這樣就不用每次都通過耗時的findViewById操作了。

這一步的優化,在listitem佈局越複雜的時候效果越為明顯。

[轉載請保留本文地址:http://www.cnblogs.com/goagent/p/5158064.html] 

三、實現局部刷新

OK,到目前為止,listview普遍需要的優化已經做的差不多了,那就該考慮實際使用場景中的優化需求了。

實際使用listview過程中,通常會在後臺更新listview的數據,然後調用Adatper的notifyDataSetChanged方法來更新listview的UI。

那麼問題來了,一般情況下,一次只會更新listview的一條/幾條數據,而調用notifyDataSetChanged方法則會把所有可視範圍內的listitem都刷新一遍,這是不科學的!

所以,進一步優化的空間在於,局部刷新listview,話不多說見代碼: 

    private class AdapterOptmL3 extends BaseAdapter {
        private LayoutInflater mLayoutInflater;
        private ListView mListView;
        private ArrayList<Integer> mListData;
        
        public AdapterOptmL3(Context context, ListView listview, ArrayList<Integer> data) {
            mLayoutInflater = LayoutInflater.from(context);
            mListView = listview;
            mListData = data;
        }
        
        private class ViewHolder {
            public ViewHolder(View viewRoot) {
                txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
            }
            public TextView txt;
        }
        
        @Override
        public int getCount() {
            return mListData == null ? 0 : mListData.size();
        }

        @Override
        public Object getItem(int position) {
            return mListData == null ? 0 : mListData.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
                ViewHolder holder = new ViewHolder(convertView);
                convertView.setTag(holder);
            }
            if (convertView != null && convertView.getTag() instanceof ViewHolder) {
                updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position));
            }
            return convertView;
        }
        
        public void updateView(ViewHolder holder, Integer data) {
            if (holder != null && data != null) {
                holder.txt.setVisibility(View.VISIBLE);
                holder.txt.setText(data + "");
            }
        }
        
        public void notifyDataSetChanged(int position) {
            final int firstVisiablePosition = mListView.getFirstVisiblePosition();
            final int lastVisiablePosition = mListView.getLastVisiblePosition();
            final int relativePosition = position - firstVisiablePosition;
            if (position >= firstVisiablePosition && position <= lastVisiablePosition) {
                updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));
            } else {
                //不在可視範圍內的listitem不需要手動刷新,等其可見時會通過getView自動刷新
            }
        }
    }

修改後的Adapter新增了一個方法 public void notifyDataSetChanged(int position) 可以根據position只更新指定的listitem。

[轉載請保留本文地址:http://www.cnblogs.com/goagent/p/5158064.html] 

局部刷新番外篇

在局部刷新數據的介面中,實際上還可以再乾點事情:listview正在滾動的時候不去刷新。

具體的思路是,如果當前正在滾動,則記住一個pending任務,等listview停止滾動的時候再去刷,這樣不會造成滾動的時候刷新錯亂。代碼如下:

  1     private class AdapterOptmL3Plus extends BaseAdapter implements OnScrollListener{
  2         private LayoutInflater mLayoutInflater;
  3         private ListView mListView;
  4         private ArrayList<Integer> mListData;
  5         
  6         private int mScrollState = SCROLL_STATE_IDLE;
  7         private List<Runnable> mPendingNotify = new ArrayList<Runnable>();
  8         
  9         public AdapterOptmL3Plus(Context context, ListView listview, ArrayList<Integer> data) {
 10             mLayoutInflater = LayoutInflater.from(context);
 11             mListView = listview;
 12             mListData = data;
 13             mListView.setOnScrollListener(this);
 14         }
 15         
 16         private class ViewHolder {
 17             public ViewHolder(View viewRoot) {
 18                 txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
 19             }
 20             public TextView txt;
 21         }
 22         
 23         @Override
 24         public int getCount() {
 25             return mListData == null ? 0 : mListData.size();
 26         }
 27 
 28         @Override
 29         public Object getItem(int position) {
 30             return mListData == null ? 0 : mListData.get(position);
 31         }
 32 
 33         @Override
 34         public long getItemId(int position) {
 35             return position;
 36         }
 37 
 38         @Override
 39         public View getView(int position, View convertView, ViewGroup parent) {
 40             if (convertView == null) {
 41                 convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
 42                 ViewHolder holder = new ViewHolder(convertView);
 43                 convertView.setTag(holder);
 44             }
 45             if (convertView != null && convertView.getTag() instanceof ViewHolder) {
 46                 updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position));
 47             }
 48             return convertView;
 49         }
 50         
 51         public void updateView(ViewHolder holder, Integer data) {
 52             if (holder != null && data != null) {
 53                 holder.txt.setVisibility(View.VISIBLE);
 54                 holder.txt.setText(data + "");
 55             }
 56         }
 57         
 58         public void notifyDataSetChanged(final int position) {
 59             final Runnable runnable = new Runnable() {
 60                 @Override
 61                 public void run() {
 62                     final int firstVisiablePosition = mListView.getFirstVisiblePosition();
 63                     final int lastVisiablePosition = mListView.getLastVisiblePosition();
 64                     final int relativePosition = position - firstVisiablePosition;
 65                     if (position >= firstVisiablePosition && position <= lastVisiablePosition) {
 66                         if (mScrollState == SCROLL_STATE_IDLE) {
 67                             //當前不在滾動,立刻刷新
 68                             Log.d("Snser", "notifyDataSetChanged position=" + position + " update now");
 69                             updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));
 70                         } else {
 71                             synchronized (mPendingNotify) {
 72                                 //當前正在滾動,等滾動停止再刷新
 73                                 Log.d("Snser", "notifyDataSetChanged position=" + position + " update pending");
 74                                 mPendingNotify.add(this);
 75                             }
 76                         }
 77                     } else {
 78                         //不在可視範圍內的listitem不需要手動刷新,等其可見時會通過getView自動刷新
 79                         Log.d("Snser", "notifyDataSetChanged position=" + position + " update skip");
 80                     }
 81                 }
 82             };
 83             runnable.run();
 84         }
 85 
 86         @Override
 87         public void onScrollStateChanged(AbsListView view, int scrollState) {
 88             mScrollState = scrollState;
 89             if (mScrollState == SCROLL_STATE_IDLE) {
 90                 //滾動已停止,把需要刷新的listitem都刷新一下
 91                 synchronized (mPendingNotify) {
 92                     final Iterator<Runnable> iter = mPendingNotify.iterator();
 93                     while (iter.hasNext()) {
 94                         iter.next().run();
 95                         iter.remove();
 96                     }
 97                 }
 98             }
 99         }
100 
101         @Override
102         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
103         }
104     }
View Code

 

[轉載請保留本文地址:http://www.cnblogs.com/goagent/p/5158064.html] 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 準備工作:導入1.activity_mian.xml2.acitivy_news.xml 3.news_item.xm...
  • 在ios中,使用多線程有三種方式,分別是:NSThread、NSOperation和NSOperationQueue、GCD,在本節,主要講解一下CDD的使用。 GCD(Grand Central Dispatch) ,他是基於C語言開發的一套多線程開發機制,也是目前蘋果官方推薦的多線程開發方...
  • Android開發中有時會遇到這種情況,在數據列表的Activity中點擊添加按鈕,彈出另一個Activity添加數據,這樣返回數據列表的Activity時就需要刷新數據列表(因為添加了一條數據)。這時需要刷新數據列表的代碼就應該寫在數據列表Activity的onStart()函數裡面,而不能寫在o...
  • 一,效果圖。二,工程圖。三,代碼。RootViewController.m#import "RootViewController.h"@interface RootViewController ()@end@implementation RootViewController- (id)initWit...
  • 只有一個蘋果開發集合網站地址的隨筆
  • 1 前言模板方法模式是面向對象軟體設計中一種非常簡單的設計模式。其基本思想是在抽象類的一個方法定義“標準”演算法。在這個方法中調用的基本操作由子類重載予以實現。這個方法成為“模板”。因為方法定義的演算法缺少一些特有的操作。2 詳述2.1 簡述定義一個操作中演算法的骨架,而將一些步驟延遲到子類中。模板方法使...
  • /設置按鈕上的自體的大小//[btn setFont: [UIFont systemFontSize: 14.0]]; //這種可以用來設置字體的大小,但是可能會在將來的SDK版本中去除改方法//應該使用btn.titleLabel.font = [UIFont systemFontOfSize: ...
  • 寫這篇博客的目的是為了跟大家分享本人對多線程的一些淺顯的理解,順道梳理一遍自己掌握的知識,如有不妥之處,歡迎各位大牛指正.首先要理解兩個概念:進程(Process):程式的一次運行,擁有獨立的記憶體地址空間(一個iOS應用只有一個進程).線程(Thread):1.線程是進程的基本執行單元,是操作系統可...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...