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

相关文章
相关标签/搜索