Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> 一步一步實現listview加載的性能優化,listview性能優化

一步一步實現listview加載的性能優化,listview性能優化

編輯:關於android開發

一步一步實現listview加載的性能優化,listview性能優化


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] 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved