Android基础之打造自己的魔法万能适配器

本文介绍了一种高效实现ListView适配器的方法,通过自定义通用ViewHolder和CommonAdapter,简化了ListView项目的开发流程。
**

在学习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的点击状态,如果点击了,就把点击的位置记下来,如果没有点击则将对象移除。

这里写图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值