android BaseAdapter優化

1.getCount()方法:html

  android提供了N多已經封裝好的適配器,但用得最多仍是BaseAdapter。若是寫一個類繼承BaseAdapter,則會看到它至少要覆寫四個方法:java

public class MAdapter extends BaseAdapter{

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        return null;
    }

}

  首先說一下getCount()方法,該方法返回一個int型的值,表示你的組件(ListView,GridView等)的長度,也就是要顯示的item的數量。若是你的getCount()返回值是0的話,列表一行都不會顯示,若是返回1,就只顯示一行。返回幾則顯示幾行。咱們通常會向adapter傳遞咱們的數據項list(好比mList),通常getCount()方法會這麼寫:android

@Override
    public int getCount() {
        // TODO Auto-generated method stub
        if (mList != null)
            return mList.size();
        else 
            return 0;
    }

  但有時,好比你使用ListView,佈局給它的位置能顯示10個childitem,當傳入的mList數量小於10,這個listView就只顯示當前的子項數目而沒法佔滿空間。有時爲了美觀,會讓ListView至少顯示10個(此時要本身處理,把多出來的子項顯示爲默認樣式),因此也會這樣寫:緩存

private static final int ITEM_COUNT = 10;
    
    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        if(mList != null && mList.size() > 10)
            return mList.size();
        else {
            return ITEM_COUNT;
        }
    }

 

 

2.getView方法(重點是convertView參數的緩存機制)ide

  那麼以ListView爲例,來講下系統如何繪製ListView(也就是如何加載,其餘須要適配器的組件均相似)。ListView 針對每一個item,要求 adapter 「返回一個視圖」 (getView()方法),也就是說ListView在開始繪製的時候,系統首先調用getCount()函數,根據他的返回值獲得ListView的長度,而後根據這個長度,調用getView()一行一行的繪製ListView的每一項。因而,咱們來看看getView()方法:函數

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        return null;
    }

這個方法會返回一個View,顯示出來就是ListView的一個子項(Child)。該方法有3個參數:佈局

  position:從0開始,能夠理解爲adapter中數據集合的下標。一般狀況下,這個position和你傳入adapter的mList.get(position)中的position對應使用。性能

  convertView:這個convertView其實就是最關鍵的部分了。原理上講,當ListView滑動的過程當中 會有item被滑出屏幕 而再也不被使用 這時候Android會回收這個條目的view,這個view也就是這裏的convertView。優化

 

  其實到此爲止咱們能夠總結出convertview的機制了,就是在初始顯示的時候,每次顯示一個item都調用一次getview方法可是每次調用的時候covertview爲空(由於尚未舊的view),當顯示完了以後。若是屏幕移動了以後,而且致使有些Item(也能夠說是view)跑到屏幕外面,此時若是還有新的item須要產生,則這些item顯示時調用的getview方法中的convertview參數就不是null,而是那些移出屏幕的view(舊view),咱們所要作的就是將須要顯示的item填充到這些回收的view(舊view)中去,最後注意convertview爲null的不只僅是初始顯示的那些item,還有一些是已經開始移入屏幕可是尚未view被回收的那些item。ui

  上圖中,當item1滾出屏幕,而且一個新的項目從屏幕低端上來時,ListView再請求一個View。convertView此時不是空值了,它的值是item1。你只需設定新的數據而後返回convertView,沒必要從新new一個View。

  最後一個parent參數暫時沒用到過。

  不使用convertView的寫法,每次都會new一個View,邏輯上沒有任何問題,可是數據量很大的話,動不動就會OOM:

public View getView(int position, View convertView, ViewGroup parent) {
View view
= new View(); //經過inflate等找到佈局 而後findViewById等 設置各個顯示的item return view; }

  使用convertView,節省了new View的大量開銷:

public View getView(int position, View convertView, ViewGroup parent) {
    View view = null;
    if (convertView != null) {
        view = convertView;
        //複用了回收的view 只須要直接做內容填充的修改就行了
    } else {
    view = new Xxx(...);
    //沒有供複用的view 按通常的作法新建view
    }
    return view;
}

 

 

3.ViewHolder和getTag()、setTag()方法:

  先上一份使用ViewHolder自定義adapter的典型寫法:

public class MarkerItemAdapter extends BaseAdapter
{
    private Context mContext = null;
    private List<MarkerItem> mMarkerData = null;

    public MarkerItemAdapter(Context context, List<MarkerItem> markerItems)
    {
        mContext = context;
        mMarkerData = markerItems;
    }

    public void setMarkerData(List<MarkerItem> markerItems)
    {
        mMarkerData = markerItems;
    }

    @Override
    public int getCount()
    {
        int count = 0;
        if (null != mMarkerData)
        {
            count = mMarkerData.size();
        }
        return count;
    }

    @Override
    public MarkerItem getItem(int position)
    {
        MarkerItem item = null;

        if (null != mMarkerData)
        {
            item = mMarkerData.get(position);
        }

        return item;
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {
        ViewHolder viewHolder = null;
        if (null == convertView)
        {
            viewHolder = new ViewHolder();
            LayoutInflater mInflater = LayoutInflater.from(mContext);
            convertView = mInflater.inflate(R.layout.item_marker_item, null);

            viewHolder.name = (TextView) convertView.findViewById(R.id.name);
            viewHolder.description = (TextView) convertView
                    .findViewById(R.id.description);
            viewHolder.createTime = (TextView) convertView
                    .findViewById(R.id.createTime);

            convertView.setTag(viewHolder);
        }
        else
        {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        // set item values to the viewHolder:

        MarkerItem markerItem = getItem(position);
        if (null != markerItem)
        {
            viewHolder.name.setText(markerItem.getName());
            viewHolder.description.setText(markerItem.getDescription());
            viewHolder.createTime.setText(markerItem.getCreateDate());
        }

        return convertView;
    }

    private static class ViewHolder
    {
        TextView name;
        TextView description;
        TextView createTime;
    }

}

  getTag()、setTag()方法很簡單:View中的setTag(Object)表示給View添加一個額外的數據,之後能夠用getTag()將這個數據取出來。

  能夠看到ViewHolder就是一個內部類。當convertView爲null時,老老實實加載xml佈局文件,而後用findViewById把佈局文件中的各個組件對應到ViewHolder的各個屬性,並把ViewHolder用setTag()方法綁定到convertView。當convertView不爲空能夠複用時,直接使用getTag()方法取出綁定的ViewHolder,省去了findViewById()的開銷。

 

4.getItemViewType(int position)和getViewTypeCount()方法

  可是上面的程序仍然有缺陷——當咱們的ListView中填充的item有多種形式時,好比微博中,有的item中包含圖片,有的item包含視頻,那麼必然的,咱們須要用到2種item的佈局方式。此時若是隻是單純判斷convertView是否存在,會形成回收的view不符合你當前須要的佈局,而相似轉換失敗出錯退出。這裏要提到Adapter中的另外2個方法:

  public int getItemViewType(int position) {}
  public int getViewTypeCount() {}

  從方法名上 就能夠比較明顯的明白這2個的做用
  下面附上一個demo代碼:

class MyAdapter extends BaseAdapter{
  Context mContext;
  LinearLayout linearLayout = null;
  LayoutInflater inflater;
  TextView tex;
  final int VIEW_TYPE = 2;
  final int TYPE_1 = 0;
  final int TYPE_2 = 1;

  public MyAdapter(Context context) {
    mContext = context;
    inflater = LayoutInflater.from(mContext);
  }

  @Override
  public int getCount() {
    return listString.size();
  }

  //每一個convert view都會調用此方法,得到當前所須要的view樣式
  @Override
  public int getItemViewType(int position) {
    int p = position%6;
    if(p == 0)
      return TYPE_1;
    else if(p < 3)
      return TYPE_2;
    else
      return TYPE_1;
  }

  @Override
  public int getViewTypeCount() {
    return 2;
  }

  @Override
  public Object getItem(int arg0) {
    return listString.get(arg0);
  }

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

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    viewHolder1 holder1 = null;
    viewHolder2 holder2 = null;
    int type = getItemViewType(position);

    //無convertView,須要new出各個控件
    if(convertView == null)
    {
    //按當前所需的樣式,肯定new的佈局
      switch(type)
      {
      case TYPE_1:
        convertView = inflater.inflate(R.layout.listitem1, parent, false);
        holder1 = new viewHolder1();
        holder1.textView = (TextView)convertView.findViewById(R.id.textview1);
        holder1.checkBox = (CheckBox)convertView.findViewById(R.id.checkbox);
        convertView.setTag(holder1);
        break;
      case TYPE_2:
        convertView = inflater.inflate(R.layout.listitem2, parent, false);
        holder2 = new viewHolder2();
        holder2.textView = (TextView)convertView.findViewById(R.id.textview2);
        holder2.imageView = (ImageView)convertView.findViewById(R.id.imageview);
        convertView.setTag(holder2);
        break;
      }
    }
    else
    {
    //有convertView,按樣式,取得不用的佈局
      switch(type)
      {
      case TYPE_1:
        holder1 = (viewHolder1) convertView.getTag();
        break;
      case TYPE_2:
        holder2 = (viewHolder2) convertView.getTag();
        break;
      }
      //設置資源
      switch(type)
      {
      case TYPE_1:
        holder1.textView.setText(Integer.toString(position));
        holder1.checkBox.setChecked(true);
        break;
      case TYPE_2:
        holder2.textView.setText(Integer.toString(position));
        holder2.imageView.setBackgroundResource(R.drawable.icon);
        break;
      }
    }
    return convertView;
  }
}
//各個佈局的控件資源
class viewHolder1{
  CheckBox checkBox;
  TextView textView;
}
class viewHolder2{
  ImageView imageView;
  TextView textView;
}

  以上基本就是主要的內容了,下面再補充實際操做當中的一些Tips
  *若是convertView上用Type區分有些繁瑣,或者不須要那麼複雜,只是不多有出現不一樣的狀況,那麼還能夠在取得convertView後,經過java提供的instanceof來判斷是否能夠強轉,若是不能強轉,就去新建一個View的作法,可是其實這種作法並不規範,因此仍是推薦上面的作法。
  *第二個是關於ListView,對於純色的item背景,其實能夠直接設置BackgroundColor,而不要使用圖片,這一部分其實能夠有不小的提高,一樣的,對於任何純色的背景,應該儘可能去設置RGB顏色,而不是全用一張圖片作背景返回。

 

5.在onItemClickLinstener()中監聽點擊事件,更改Item背景的問題

  我之前都是這麼寫的

mListView.setOnItemClickListener(new OnItemClickListener() {

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
view.findViewById(R.id.background).setBackgroundResource(R.drawable.bg_seleted);
} });

  onItemClick()方法中第二個參數view就是mListView使用的adapter中,getView()方法返回的View,第三個參數position也對應getView()方法的position參數。這樣寫乍看之下沒有任何問題,可是若是在adapter中使用了convertView來優化適配器,那麼getView()方法返回的極可能是緩存的convertView。這彷佛也沒有問題,由於每次點擊時的item必然已經加載在可見的位置。可是,若是要這樣,我作了一個橫向的list,若是要他來顯示某些配置信息:

  如上圖,點擊item以後就會使背景變化(加上白色背景框了),再點擊以後背景又會回到默認狀態,並且一頁最多顯示5個item。好,如今我把第二個、第三個,以及第30個圖標item點亮了,並把這些信息保存起來,以便下次進入程序時仍然讓這幾個item是點亮的狀態。因而,下次進入程序時,初始化階段我須要讀取這些保存的信息,把須要點亮的圖標item繪製出來。怎麼作呢?我是這樣作的:

mListView.getChildAt(idex).findViewById(R.id.background).setBackgroundResource(R.drawable.bg_seleted);

  好,問題來了,空指針!

  加載第2、三個item是沒有問題的,由於進入程序時listview會首先加載最早顯示的5個item,第6個呢,可能也會加載,這樣滑動的時候纔不至於措手不及。但是第30個就難說了。也許滑動到第二十多個的時候纔會加載——到了須要使用的時候才加載,這是性能最優的選擇。可是在初始化的時候mListView.getChild(30)返回就是null,很差意思,程序崩了……

  怎麼辦呢?

  被領導罵了無數次以後,終於認識到不能用上面那種語句。先回過頭來,看一下本文第3個標題下的那個典型BaseAdapter寫法,就是那個MarkerItemAdapter,它的構造中傳入了一個List<MarkerItem>,這個就是一般要顯示在listView裏的數據表,在MarkerItem類裏曾加一條屬性吧,好比int state = 0;在onItemClick()裏改變state的值:

markerItems.get(position).setState(1);

  而後在adapter的getView()方法中根據item的state屬性來加載對應的背景便可。

@Override
    public View getView(int position, View convertView, ViewGroup parent)
    {
        ViewHolder viewHolder = null;
        if (null == convertView)
        {
            viewHolder = new ViewHolder();
            LayoutInflater mInflater = LayoutInflater.from(mContext);
            convertView = mInflater.inflate(R.layout.item_marker_item, null);

            viewHolder.name = (TextView) convertView.findViewById(R.id.name);
            viewHolder.description = (TextView) convertView
                    .findViewById(R.id.description);
            viewHolder.createTime = (TextView) convertView
                    .findViewById(R.id.createTime);

            convertView.setTag(viewHolder);
        }
        else
        {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        // set item values to the viewHolder:

        MarkerItem markerItem = getItem(position);
        if (null != markerItem)
        {
            viewHolder.name.setText(markerItem.getName());
            viewHolder.description.setText(markerItem.getDescription());
            viewHolder.createTime.setText(markerItem.getCreateDate());
              if (markerItem.getState() == 0) { holder.bgLayout.setBackgroundResource(R.drawable.bg_default); } else if (markerItem.getState() == 1) { holder.bgLayout.setBackgroundResource(R.drawable.bg_seleted); }
        }
        return convertView;
    }

  這樣,到了須要加載的時候才加載,並且能加載出正確的數據。因此在操做有適配器的組件時,務必直接更改本身的數據集,而不是更改listView等的itemView纔是最穩妥的作法。最後不要忘了在點擊事件最後通知adapter更新:

mAdapter.notifyDataSetChanged();

  多麼痛的領悟……

 

 

  參考博文:1.http://blog.chinaunix.net/uid-11798215-id-3407345.html

                    2.http://mzh3344258.blog.51cto.com/1823534/889879

                    3.http://www.cnblogs.com/over140/archive/2011/03/23/1991100.html

                    4.http://www.cnblogs.com/mengdd/p/3254323.html

相關文章
相關標籤/搜索