Android适配器-附万能适配器工具类

在我们使用listview,gridview及expandListview时,经常会使用viewHolder去优化他们的加载.

首先,我们先看一下为什么要使用viewHolder:以listview来讲,listview的加载时是只加载当前屏幕可见的和即将可见的item,对于滑动后不可见的item,将会被系统回收,放在即将可见的item中.所以我们虽然不断滑动看见的是不同的item内容,但是本质上只有那么几个itemview在负责展示数据,所以针对这种情况谷歌建议我们使用viewHolder进行listview的优化.谷歌建议的listview优化分为两个方面,一个是convertView的复用,通过在getview中判断每次的convertView是否为空,如果为空则重新inflate.这样可以减少inflateview()的次数代码如下:

<span style="font-size:18px;">if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
                ((ImageView) convertView.findViewById(R.id.icon1)).setImageResource(R.drawable.icon);               
            } else{
                return convertView;</span>
 
另一个是通过viewHolder减少findviewbuid的次数来进行优化,我们知道每个convertveiw对应一个item,所以当我们的convertview复用时,它里面的子view也应该不需要重新findviewbyId()去查找子控件,所以这时我们需要引入viewHolder类来保存convertview中的子控件实例.通过convertView.settag(viewHolder)将viewHolder的实例保存到convertview的tag中,然后每次我们在getview()中判断convertview不为空后,使用convertview.gettag()获取该convertview保存的对应viewHolder实例.具体代码如下: 
<span style="font-size:18px;">public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item_icon_text,
                        null);
                holder = new ViewHolder();
		holder.text1 = (TextView) convertView.findViewById(R.id.text1);           
                convertView.setTag(holder);
            }
            else{
                holder = (ViewHolder)convertView.getTag();
            }
            holder.icon1.setImageResource(R.drawable.icon); 
            return convertView;
        }
    }

    static class ViewHolder {
        TextView text1;
      
    }</span>
 

以上就是listview使用viewHolder进行性能优化的原理和方法了.但是在我们实际开发过程中,使用viewholder和继承baseAdapter时有很多的重复的代码,在这里我们就把他们抽取出来,作为工具类减少大家在开发过程中的重复劳动,首先我们看下使用工具类comonHolder及commonAdapter的实际效果,我们拿一个gridView的adapter实现来说,代码如下所示:

<span style="font-size:18px;">public class GridAdapter extends CommonAdapter<String> {


	public GridAdapter(Context context, List<String> datas) {
		super(context, datas);
	}


	public View getView(int position, View convertView, ViewGroup parent) {
		CommonHolder holder = CommonHolder.get(context, convertView, parent,
				R.layout.gridview_item, position);
		convertView = holder.getConvertView();
		holder.tvSetText(R.id.tv, datas.get(position));
		holder.tvSetText(R.id.tv_top, (position + 1) + "月");
		return convertView;
	}
}</span>
 

大家可以看到我们就这样只使用几行代码就已经完成一个gridView的adapter代码编写,究竟如何实现的呢,下面我对于我们的commonAdaper及commonHolder的实现做一些分析:首先对于commonHolder,我们先分析一下普通的viewHolder类,它的实现是定义一个类,这个类的成员变量是要存放的控件,我们通过给viewHolder进行实例化获得这些控件的实例,但是我们要对holder类进行封装,每次holder类中的控件数量是不一定的,所以应该有一个数据结构去存放这些每个holder类中的控件实例,我们知道每个控件的id可以唯一区分这些控件,所以我们可以使用HashMao<int,view>来存放holder中控件实例,但是Android对于这种key为int,值为obj的数据会建议我们使用sparseArray来存储,所以我们就使用sparseArray来存储每个holder的view实例(关于sparseArray将在文章后面给出介绍),确定了数据结构,来分析我们的commonHolder:

<span style="font-size:18px;">/**万能holder
 * @author CK
 *
 */
public class CommonHolder {
	private SparseArray<View> mViews;
	private int mPositon;
	private View mConvertView;
	//将构造方法私有化,只有当传入的convertView为空时,才会在内部调用.
	private CommonHolder(Context context, ViewGroup parent, int layoutId,
			int position) {
		this.mPositon = position;
		//新建一个sparsArray用于存储view
		this.mViews = new SparseArray<View>();
		//inflate convertView
		this.mConvertView = LayoutInflater.from(context).inflate(layoutId,
				parent, false);
		//将当前holder的实例setTag
		mConvertView.setTag(this);

	}

	public static CommonHolder get(Context context, View convertView,
			ViewGroup parent, int layoutId, int position) {
		if (convertView == null) {
			//如果converView为空则调用私有化的构造方法
			return new CommonHolder(context, parent, layoutId, position);
		} else {
			//如果传入的convertView不为空,则取出holder,并将position赋值给holder的position
			CommonHolder holder = (CommonHolder) convertView.getTag();
			holder.mPositon = position;
			return holder;
		}

	}
	//获取convertView的方法
	public View getConvertView() {
		return mConvertView;
	}
	//获取item中view的方法,传入viewId.(使用的泛型会在结尾介绍)
	public <T extends View> T getViewItem(int viewId) {
		//在sparseArray中利用viewId获取view
		View view = mViews.get(viewId);
		if (view == null) {
			//如果view为空,说明该view需要find,find完成后保存到sparseArray
			view = mConvertView.findViewById(viewId);
			mViews.put(viewId, view);
		}
		return (T) view;
	}

	public CommonHolder tvSetText(int viewId, String text) {
		TextView tv = getViewItem(viewId);
		tv.setText(text);
		return this;
	}
}</span>

接下来是我们的commonAdapter,在我们继承BaseAdapter时,getcount(),getItem()等一些方法是重复写的,所以我们可以把他们封装成一个commonAdapter类,当我们使用的时候直接继承就可以了,具体代码如下:

/**抽象类,将getview方法定义为抽象方法,这样继承commonAdapter只需要重写getView方法就可以了.
 * @author CK
 *
 * @param <T> 数据类型
 */
public abstract class CommonAdapter<T> extends BaseAdapter {

	public Context context;
	public List<T> datas;
	
	protected CommonAdapter(Context context, List<T> datas) {
		this.context = context;
		this.datas = datas;
	}

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

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

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

	/* 将getView定义为抽象方法,当我们继承commonAdapter类时会必须复写getView方法
	 * 
	 */
	@Override
	public abstract View getView(int position, View convertView, ViewGroup parent);

}
关于commonHolder及commonAdapter具体的使用方法,可以参照gridView的adapter那段代码,接下来是关于expandListView的commonAdapter代码:

public abstract class CommonExpandAdapter<T> extends BaseExpandableListAdapter {
	protected List<T> groupData;
	protected List<List<T>> childData;
	protected Context context;

	// 初始化adapter
	public CommonExpandAdapter(List<T> groupData, List<List<T>> childData,
			Context context) {
		this.groupData = groupData;
		this.childData = childData;
		this.context = context;
		
	}

	@Override
	public int getGroupCount() {
		if (groupData != null) {
			return groupData.size();
		} else {
			return 0;
		}
	}

	@Override
	public int getChildrenCount(int groupPosition) {
		if (childData.get(groupPosition) != null) {
			return childData.get(groupPosition).size();
		} else {
			return 0;
		}
	}

	@Override
	public Object getGroup(int groupPosition) {
		if (groupData != null) {
			return groupData.get(groupPosition);
		} else {
			return null;
		}
	}

	@Override
	public Object getChild(int groupPosition, int childPosition) {
		if (childData.get(groupPosition) != null) {
			return childData.get(groupPosition);
		} else {
			return null;
		}
	}

	@Override
	public long getGroupId(int groupPosition) {
		if (groupData != null) {
			return groupPosition;
		} else {
			return 0;
		}
	}

	@Override
	public long getChildId(int groupPosition, int childPosition) {
		if (childData.get(groupPosition) != null) {
			return childPosition;
		} else {
			return 0;
		}
	}

	@Override
	public boolean hasStableIds() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public abstract View getGroupView(int groupPosition, boolean isExpanded,
			View convertView, ViewGroup parent);

	@Override
	public abstract View getChildView(int groupPosition, int childPosition,
			boolean isLastChild, View convertView, ViewGroup parent);

	@Override
	public boolean isChildSelectable(int groupPosition, int childPosition) {
		// TODO Auto-generated method stub
		return false;
	}

}
同样的,我们只需要继承commonExpandAdapter,然后复写其中的getGroupView和getChildView即可.最后我们看一下关于sparseArray和泛型.

首先关于sparseArray,sparseArray是一种Android的数据结构,主要是针对key为int,value为Obj类型的map进行的优化,不仅可以节省存储空间,同时因为使用二分查找,所以查询效率也很高.sparseArray是指稀疏数组,所谓稀疏数组就是数组中大部分的内容值都未被使用(或都为零),在数组中仅有少部分的空间使用。因此造成内存空间的浪费,为了节省内存空间,并且不影响数组中原有的内容值,我们可以采用一种压缩的方式来表示稀疏数组的内容。


进行压缩后为:

接下来是关于泛型:例如我们的commonAdapter中,因为每次的传入的数据类型不一定,所以我们需要使用泛型来告诉编译器,我将会传一个什么样的数据给你.

1 定义带类型参数的类
  在定义带类型参数的类时,在紧跟类命之后的<>内,指定一个或多个类型参数的名字,同时也可以对类型参数的取
  值范围进行限定,多个类型参数之间用,号分隔。
  定义完类型参数后,可以在定义位置之后的类的几乎任意地方(静态块,静态属性,静态方法除外)使用类型参数,
  就像使用普通的类型一样。
  注意,父类定义的类型参数不能被子类继承。
 public class TestClassDefine<T, S extends T> {
     ....  

  }

 

2 定义待类型参数方法

 在定义带类型参数的方法时,在紧跟可见范围修饰(例如public)之后的<>内,指定一个或多个类型参数的名字,

 同时也可以对类型参数的取值范围进行限定,多个类型参数之间用,号分隔。

 定义完类型参数后,可以在定义位置之后的方法的任意地方使用类型参数,就像使用普通的类型一样。

 例如:

 public <T, S extends T> T testGenericMethodDefine(T t, S s){

     ...

 }

 注意:定义带类型参数的方法,骑主要目的是为了表达多个参数以及返回值之间的关系。例如本例子中T和S的继

 承关系, 返回值的类型和第一个类型参数的值相同。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值