**
在学习android的路上我们和适配器打的交道最多了,我们通常的做法是每个Listview和Gridview都会去写一个Adapter,如果我们是去写一个项目的话那我们要重复去写许多适配器,这显然不是我们愿意做的,我们要想办法去偷懒,怎么不去写一个万能的适配器呢?那就驱使着写一个万能的适配器了。(可以节省时间打一盘LOL或者是看一部电影,想一想都是多么惬意的一件事呢)。
**
先来看一下平时用的适配器怎么使用:
主布局文件:
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
item的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:descendantFocusability="blocksDescendants"
>
<TextView
android:id="@+id/title_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textSize="16sp"
android:textColor="#444"
android:text="Android新技能"/>
<TextView
android:id="@+id/desc_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title_tv"
android:layout_marginTop="10dp"
android:minLines="1"
android:maxLines="2"
android:textSize="16sp"
android:textColor="#898989"
android:text="Android打造万能适配器"/>
<TextView
android:id="@+id/time_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/desc_tv"
android:layout_marginTop="10dp"
android:textSize="12sp"
android:textColor="#DF4F44"
android:text="2016-5-20"/>
<TextView
android:id="@+id/phone_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/time_tv"
android:layout_alignParentRight="true"
android:layout_below="@+id/desc_tv"
android:background="#2ED667"
android:drawableLeft="@drawable/icon"
android:drawablePadding="5dp"
android:padding="3dp"
android:text="10086"
android:textColor="#fff"
android:textSize="12sp" />
<CheckBox
android:id="@+id/check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/desc_tv"
android:layout_alignParentRight="true" />
</RelativeLayout>
平常的适配器:
package com.example.bestAdapter.myAdapter;
import java.util.List;
import com.example.bestAdapter.R;
import com.wxample.bastAdapter.bean.Bean;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class MyAdapter extends BaseAdapter{
private Context context;
private List<Bean> mData;
private LayoutInflater mInflater;
private int resId;
public MyAdapter(Context context, List<Bean> mData,int resId){
this.context=context;
this.mData=mData;
this.resId=resId;
mInflater=LayoutInflater.from(context);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mData.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return mData.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHodler hodler=null;
// TODO Auto-generated method stub
/*ViewHolder holder=ViewHolder.get(context, position, convertView, parent, resId);
holder.getView(R.id.title_tv).setText(mData.get(position).getTitle());
holder.getView(R.id.desc_tv).setText(mData.get(position).getTitle());
holder.getView(R.id.time_tv).setText(mData.get(position).getTitle());
holder.getView(R.id.phone_tv).setText(mData.get(position).getTitle());*/
if (convertView==null) {
convertView=mInflater.inflate(R.layout.myadapter_item, parent, false);
hodler=new ViewHodler();
hodler.title_tv=(TextView) convertView.findViewById(R.id.title_tv);
hodler.desc_tv=(TextView) convertView.findViewById(R.id.desc_tv);
hodler.time_tv=(TextView) convertView.findViewById(R.id.time_tv);
hodler.phone_tv=(TextView) convertView.findViewById(R.id.phone_tv);
convertView.setTag(hodler);
}else{
hodler=(ViewHodler) convertView.getTag();
}
Bean bean=mData.get(position);
hodler.title_tv.setText(bean.getTitle());
hodler.desc_tv.setText(bean.getTitle());
hodler.time_tv.setText(bean.getTitle());
hodler.phone_tv.setText(bean.getTitle());
return convertView;
}
static class ViewHodler{
TextView title_tv;
TextView desc_tv;
TextView time_tv;
TextView phone_tv;
}
}
上面这个例子大家应该都写了无数遍了,MyAdapter集成BaseAdapter,然后getView里面使用ViewHolder模式;一般情况下,我们的写法是这样的:对于不同布局的ListView,我们会有一个对应的Adapter,在Adapter中又会有一个ViewHolder类来提高效率。
这样出现ListView就会出现与之对于的Adapter类、ViewHolder类;那么有没有办法减少我们的编码呢?
下面首先拿ViewHolder开刀~
创建新的通用的ViewHolder
首先分析下ViewHolder的作用,通过convertView.setTag与convertView进行绑定,然后当convertView复用时,直接从与之对于的ViewHolder(getTag)中拿到convertView布局中的控件,省去了findViewById的时间~
也就是说,实际上们每个convertView会绑定一个ViewHolder对象,这个viewHolder主要用于帮convertView存储布局中的控件。
那么我们只要写出一个通用的ViewHolder,然后对于任意的convertView,提供一个对象让其setTag即可;
既然是通用,那么我们这个ViewHolder就不可能含有各种控件的成员变量了,因为每个Item的布局是不同的,最好的方式是什么呢?
提供一个容器,专门存每个Item布局中的所有控件,而且还要能够查找出来;既然需要查找,那么ListView肯定是不行了,需要一个键值对进行保存,键为控件的Id,值为控件的引用,相信大家立刻就能想到Map;但是我们不用Map,因为有更好的替代类,就是我们android提供的SparseArray这个类,和Map类似,但是比Map效率,不过键只能为Integer.
ViewHolder类:
package com.example.administrator.myapplication.util;
import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
/**
* Created by Administrator on 2016/5/22.
*/
public class ViewHolder {
private Context mContext;
private int mPosition;
private SparseArray<View> mViews=new SparseArray<>();
private View mConvertView;
//ViewHolder的构造方法,
public ViewHolder(Context context, int position, View convertView, ViewGroup parent,int layoutId){
convertView= LayoutInflater.from(context).inflate(layoutId,parent,false);
this.mContext=context;
this.mPosition=position;
this.mConvertView=convertView;
convertView.setTag(this);
}
//入口方法,判断ConvertView是否为空
public static ViewHolder get(Context context, int position, View convertView, ViewGroup parent,int layoutId){
if(convertView==null)
{
//如果为空就利用构造函数创造ViewHolder实例
return new ViewHolder( context, position, convertView, parent, layoutId);
}else
{
//如果不为空直接设置数据上去,返回ViewHolder对象实例
ViewHolder holder= (ViewHolder) convertView.getTag();
holder.mPosition=position;
return holder;
}
}
//返回ConvertView对象,在getView中进行调用
public View getmConvertView(){
return mConvertView;
}
/*
* 设置TextView的值
* viewId
* text
*/
public ViewHolder setText(int viewId,String text){
TextView tv=getView(viewId);
tv.setText(text);
return this;
}
public int getmPosition() {
return mPosition;
}
public void setmPosition(int mPosition) {
this.mPosition = mPosition;
}
/*
* 因为要打造万能适配器,所以可能每个item有不同的控件,我们希望把控件抽象出来,
* 我们知道view是所有控件的父类,
* 所以我们让所有控件继承父类,把控件用泛型存取在 SparseArray
* 集合中,这样我们可以根据每个viewId找到控件
*/
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;
}
}
**与传统的ViewHolder不同,我们使用了一个SparseArray用于存储与之对于的convertView的所有的控件,当需要拿这些控件时,通过getView(id)进行获取;
只是打造通用的ViewHolder是远远不够的,我们还应该打造属于自己的万能Adapter**
CommonAdapter:
public abstract class CommonAdapter<T> extends BaseAdapter{
private Context mContext;
private List<T> mData;
private int mlayoutId;
public CommonAdapter(Context context,List<T>data,int layoutId) {
mContext=context;
mData=data;
mlayoutId=layoutId;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mData.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return mData.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder hodler=ViewHolder.get(mContext, position, convertView, parent, mlayoutId);
convert(hodler,mData.get(position));
return hodler.getmConvertView();
}
//将相同的方法抽象出来
public abstract void convert(ViewHolder holder, Object object);
}
因为我们存取item的数据的类不一定是相同的,所以我们应该建立一个泛型去描述这些类,当你需要用到一个类时,你应该把你的类传进去就行了。
其次,我觉得除此之外还可以继续优化,我们可以把相同的方法继续抽象出来
(ViewHolder viewHolder = getViewHolder(position, convertView,parent);)和
最后一行:return viewHolder.getConvertView();一定是一样的。
我们可以把它抽象出来,上述代码,我已经进行了优化。
为了实现这个抽象犯法convert(hodler,mData.get(position));,还有实现封装原理,我们可以再下一个魔法式的适配器:
MagicAdapter:
public class MagicAdapter extends CommonAdapter<Bean>{
private List<Bean> data;
private int flag=0;
public MagicAdapter(Context context, List<Bean> data, int layoutId) {
super(context, data, layoutId);
}
@Override
public void convert(ViewHolder holder, final Object object) {
final Bean bean=(Bean) object;
holder.setText(R.id.title_tv, bean.getTitle())
.setText(R.id.desc_tv, bean.getDesc())
.setText(R.id.time_tv, bean.getTime())
.setText(R.id.phone_tv, bean.getPhone());
}
}
方法中只有一句话就可以实现了,感觉像是魔法
方法的调用在 CommonAdapter中
现在再来看一下我们的主类:
MainActivity:
public class MainActivity extends Activity {
private ListView mlistview;
private List<Bean> mData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initView();
}
//初始化控件
private void initView(){
mlistview=(ListView) findViewById(R.id.listview);
mlistview.setAdapter(new MagicAdapter(this, mData, R.layout.myadapter_item));
}
//初始化数据
private void initData(){
mData=new ArrayList<>();
for (int i = 0; i < 15; i++) {
Bean bean=new Bean("android新技能"+i, "android打造万能适配器",
"2016-5-20", "10086");
mData.add(bean);
}
}
}
在适配器中直接new一个适配器对象,再传入参数就可以适配成功,同样只是用了一句话。
我在上述的基础上加了一个checkBox进行测试
public class MagicAdapter extends CommonAdapter<Bean> {
private List<Bean> data;
private int flag=0;
//设置标记位集合记录选中的状态
private List<Integer> mPos=new ArrayList<>();
public MagicAdapter(Context context, List<Bean> data, int layoutId) {
super(context, data, layoutId);
}
@Override
protected void convert(final ViewHolder holder, Object object) {
final Bean bean= (Bean) object;
holder.setText(R.id.title_tv, bean.getTitle())
.setText(R.id.desc_tv, bean.getDesc())
.setText(R.id.time_tv, bean.getTime())
.setText(R.id.phone_tv, bean.getPhone());
final CheckBox cb=holder.getView(R.id.check);
cb.setChecked(false);
if (mPos.contains(holder.getmPosition())){
cb.setChecked(true);
}
cb.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//进行判断,如果为选中状态,则加入集合当中,没有选中则移除这个位置对象
if (cb.isChecked()){
mPos.add(holder.getmPosition());
}else {
mPos.remove((Integer)holder.getmPosition());
}
}
});
}
}
上述我使用了一个mPos集合去存取CheckBox的点击状态,如果点击了,就把点击的位置记下来,如果没有点击则将对象移除。