Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Listview加載的性能優化是如何實現的

Listview加載的性能優化是如何實現的

編輯:關於Android編程

在android開發中Listview是一個很重要的組件,它以列表的形式根據數據的長自適應展示具體內容,用戶可以自由的定義listview每一列的布局,但當listview有大量的數據需要加載的時候,會占據大量內存,影響性能,這時候就需要按需填充並重新使用view來減少對象的創建。

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

0、最原始的加載

1、利用convertView

2、利用ViewHolder

3、實現局部刷新

〇、最原始的加載

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

private class AdapterOptmL extends BaseAdapter {
private LayoutInflater mLayoutInflater;
private ArrayList<Integer> mListData;
public AdapterOptmL(Context context, ArrayList<Integer> data) {
mLayoutInflater = LayoutInflater.from(context);
mListData = data;
}
@Override
public int getCount() {
return mListData == null ? : mListData.size();
}
@Override
public Object getItem(int position) {
return mListData == null ? : mListData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View viewRoot = mLayoutInflater.inflate(R.layout.listitem, parent, false);
if (viewRoot != null) {
TextView txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);
txt.setText(getItem(position) + "");
}
return viewRoot;
}
}

一、利用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。

經過優化後的代碼如下:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);
}
if (convertView != null) {
TextView txt = (TextView)convertView.findViewById(R.id.listitem_txt);
txt.setVisibility(View.VISIBLE);
txt.setText(getItem(position) + "");
}
return convertView;
}

上述代碼加了判斷,如果傳入的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狀態!

二、利用ViewHolder

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

private class AdapterOptmL extends BaseAdapter {

private LayoutInflater mLayoutInflater;
private ArrayList<Integer> mListData;
public AdapterOptmL(Context context, ArrayList<Integer> data) {
mLayoutInflater = LayoutInflater.from(context);
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 ? : mListData.size();
}
@Override
public Object getItem(int position) {
return mListData == null ? : 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) {
ViewHolder holder = (ViewHolder)convertView.getTag();
holder.txt.setVisibility(View.VISIBLE);
holder.txt.setText(getItem(position) + "");
}
return convertView;
}
}

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

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

三、實現局部刷新

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。

局部刷新番外篇

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

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

private class AdapterOptmLPlus extends BaseAdapter implements OnScrollListener{
private LayoutInflater mLayoutInflater;
private ListView mListView;
private ArrayList<Integer> mListData;
private int mScrollState = SCROLL_STATE_IDLE;
private List<Runnable> mPendingNotify = new ArrayList<Runnable>();
public AdapterOptmLPlus(Context context, ListView listview, ArrayList<Integer> data) {
mLayoutInflater = LayoutInflater.from(context);
mListView = listview;
mListData = data;
mListView.setOnScrollListener(this);
}
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 ? : mListData.size();
}
@Override
public Object getItem(int position) {
return mListData == null ? : 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(final int position) {
final Runnable runnable = new Runnable() {
@Override
public void run() {
final int firstVisiablePosition = mListView.getFirstVisiblePosition();
final int lastVisiablePosition = mListView.getLastVisiblePosition();
final int relativePosition = position - firstVisiablePosition;
if (position >= firstVisiablePosition && position <= lastVisiablePosition) {
if (mScrollState == SCROLL_STATE_IDLE) {
//當前不在滾動,立刻刷新
Log.d("Snser", "notifyDataSetChanged position=" + position + " update now");
updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));
} else {
synchronized (mPendingNotify) {
//當前正在滾動,等滾動停止再刷新
Log.d("Snser", "notifyDataSetChanged position=" + position + " update pending");
mPendingNotify.add(this);
}
}
} else {
//不在可視范圍內的listitem不需要手動刷新,等其可見時會通過getView自動刷新
Log.d("Snser", "notifyDataSetChanged position=" + position + " update skip");
}
}
};
runnable.run();
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mScrollState = scrollState;
if (mScrollState == SCROLL_STATE_IDLE) {
//滾動已停止,把需要刷新的listitem都刷新一下
synchronized (mPendingNotify) {
final Iterator<Runnable> iter = mPendingNotify.iterator();
while (iter.hasNext()) {
iter.next().run();
iter.remove();
}
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
}

以上所述是針對Listview加載的性能優化是如何實現的全部敘述,希望對大家有所幫助。

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