一直都是用歇菜方式写的Adapter,这种方式每次加载view,都要建立很多view对象,如果超过一定数量这种加载方式肯定要歇菜。在应用上架后,修正了用户提交的Bug后,我打算系统的对App做优化。第一步就是优化Adapter,那么就从ViewHolder开始。
不光是要让效率变高,代码也要好看,而且要增加可重用性,为以后的开发打好基础。下面是我的目标:
很幸运我直接就搜到了 hyman 老师的视频。
在ViewHolder里,用SparseArray来存储一个View,我们首先来定义变量
private SparseArray<View> mViews; private int mPosition; private View mConvertView;
然后我们在定义一个静态方法 get()
来返回这个ViewHolder
public static ViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId, int position) { if(convertView == null) { return new ViewHolder(context,parent,layoutId,position); } else { ViewHolder holder = (ViewHolder)convertView.getTag(); holder.mPosition = position; return holder; } }
在这个方法里在 converView == null
的时候我们才创建一个ViewHolder。构造函数为
public ViewHolder(Context context, ViewGroup parent, int layoutId, int position) { this.mPosition = position; this.mViews = new SparseArray<View>(); mConvertView = LayoutInflater.from(context).inflate(layoutId,parent,false); mConvertView.setTag(this); }
使用 convertView.getTag()
和 convertView.setTag()
来关联ViewHolder之后,在来改变ViewHolder都是可以的,前提是先要关联。
然后我们需要一个方法来加入和读取控件。这个方法用一个泛型来实现的
/*** * 通过viewId返回View * @param viewId * @param <T> * @return */ public <T extends View> T getView(int viewId) { View view = mViews.get(viewId); if(view == null) { view = mConvertView.findViewById(viewId); mViews.put(viewId,view); } return (T)view; }
在这个方法里通过空间id来获得控件,如果没有就从convertView里面找出来。还有一个方法是返回这个convertView
public View getConvertView() { return mConvertView; }
我创建了一个Adapter类为 RiftExAdapter 扩展至 BaseAdapter,在没有使用ViewHolder以前代码大概是这个样子的
public View getView(int position, View convertView, ViewGroup parent) { RiftInfo bean = mDatas.get(position); if(null == convertView) { convertView = ((LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE)) .inflate(R.layout.item_rift, null); } TextView rankRift = (TextView)convertView.findViewById(R.id.rankRift); TextView battletagRift = (TextView) convertView.findViewById(R.id.battletagRift); TextView riftLevelRift = (TextView) convertView.findViewById(R.id.riftLevelRift); TextView riftTimeRift = (TextView) convertView.findViewById(R.id.riftTimeRift); rankRift.setText(bean.getRank()); battletagRift.setText(bean.getBattleTag()); riftLevelRift.setText(bean.getRiftLevel()); riftTimeRift.setText(bean.getRiftTime()); return null; }
首先加入ViewHolder后代码初步修改为这个样子
public View getView(int position, View convertView, ViewGroup parent) { RiftInfo bean = mDatas.get(position); ViewHolder holder = ViewHolder.get(mContext, convertView, parent, R.layout.item_rift, position); ((TextView)holder.getView(R.id.rankRift)).setText(bean.getRank()); ((TextView)holder.getView(R.id.battletagRift)).setText(bean.getBattleTag()); ((TextView)holder.getView(R.id.riftLevelRift)).setText(bean.getRiftLevel()); ((TextView)holder.getView(R.id.riftTimeRift)).setText(bean.getRiftTime()); return holder.getConvertView(); }
运行一下看看结果
(如图1.1)
以后使用ViewHolder只需要3步
get()
方法得到holder getView()
来得到空间 holder.getConvertView()
可以节省大量的代码,尤其是ListView多了之后。
实际的App界面中还涉及到一个图片。当然如果是资源id的图片那跟直接使用setText是没有区别的。如果使用的是网络图片的话,我使用了
com.android.volley.toolbox.ImageLoader;
那么首先要申明一个变量
ImageLoader mImageLoader;
然后在 getView()
中是这样使用的
if (position > 0) { mImageLoader = MySingleton.getInstance(mContext).getImageLoader(); mImageLoader.get(bean.getSrc(), ImageLoader.getImageListener((ImageView)holder.getView(R.id.battletagImageRift), R.drawable.def_image, R.drawable.err_image)); }
在这里我使用了Volley的ImageLoader给一个ImageView异步读取了一个网络图片。
MySingleton是官方提供的一个管理Volley队列的类。
封装后的ViewHolder并不影响正常的使用。