在我们使用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的继
承关系, 返回值的类型和第一个类型参数的值相同。