Android之打造ListView的万能适配器

来源:互联网 发布:类似于上瘾的网络剧 编辑:程序博客网 时间:2024/04/28 12:53

一、Android适配器简介

在Android中,适配器扮演者重要的角色,是UI与Data实现绑定的一个桥梁。Adapter负责创建和显示每个项目的子View和提供对下层数据的访问。支持Adapter绑定的UI控件必须扩展AdapterView抽象类。

二、传统的ListView适配器写法

我们一向写的自定义适配器,就是继承ArrayAdapter,或者继承自BaseAdapter,然后重写4个方法,前三个方法基本相同,不同在于getView方法,getView里面为了减少绑定和View的重建,又会引入一个类ViewHolder。如图:

1

实现以上效果,传统的做法是这样的:

带有ListView的布局文件:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context="com.wz.adapterdemo.MainActivity">    <ListView        android:id="@+id/listView"        android:layout_width="match_parent"        android:layout_height="match_parent"/></RelativeLayout>

然后是MainActivity.java

package com.wz.adapterdemo;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.widget.ListView;import com.wz.adapterdemo.bean.Bean;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity {    private ListView listView;    private List<Bean> datas;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        listView = (ListView) findViewById(R.id.listView);        initDatas();        listView.setAdapter(new MyAdapter(this,datas));    }    private void initDatas(){        datas = new ArrayList<>();        Bean bean1 = new Bean("移动开发1","Android手机开发1","2016-11-02","000-123456");        datas.add(bean1);        Bean bean2 = new Bean("移动开发2","Android手机开发2","2016-11-02","000-123456");        datas.add(bean2);        Bean bean3 = new Bean("移动开发3","Android手机开发3","2016-11-02","000-123456");        datas.add(bean3);        Bean bean4 = new Bean("移动开发4","Android手机开发4","2016-11-02","000-123456");        datas.add(bean4);        Bean bean5 = new Bean("移动开发5","Android手机开发5","2016-11-02","000-123456");        datas.add(bean5);    }}

写一个bean类:

package com.wz.adapterdemo.bean;/** * Created by asus on 2016/11/2. */public class Bean {    private String Title;    private String desc;    private String time;    private String  phoneNumber;    public Bean(){    }    public Bean(String title, String desc, String time, String phoneNumber) {        Title = title;        this.desc = desc;        this.time = time;        this.phoneNumber = phoneNumber;    }    public String getTitle() {        return Title;    }    public void setTitle(String title) {        Title = title;    }    public String getDesc() {        return desc;    }    public void setDesc(String desc) {        this.desc = desc;    }    public String getTime() {        return time;    }    public void setTime(String time) {        this.time = time;    }    public String getPhoneNumber() {        return phoneNumber;    }    public void setPhoneNumber(String phoneNumber) {        this.phoneNumber = phoneNumber;    }}

然后自定义一个MyAdapter:

package com.wz.adapterdemo;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.TextView;import com.wz.adapterdemo.bean.Bean;import java.util.List;/** * Created by asus on 2016/11/2. */public class MyAdapter extends BaseAdapter {    private LayoutInflater mInflater;    private List<Bean> mDatas;    public MyAdapter(){    }    public MyAdapter(Context context, List<Bean> datas) {        this.mInflater = LayoutInflater.from(context);        this.mDatas = datas;    }    @Override    public int getCount() {        return mDatas.size();    }    @Override    public Object getItem(int position) {        return mDatas.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder viewHolder = null;        if(convertView == null){            convertView = mInflater.inflate(R.layout.item_listview,parent,false);            viewHolder = new ViewHolder();            viewHolder.mTitle = (TextView) convertView.findViewById(R.id.title_tv);            viewHolder.mDesc = (TextView) convertView.findViewById(R.id.desc_tv);            viewHolder.mTime = (TextView) convertView.findViewById(R.id.time_tv);            viewHolder.mPhoneNumber = (TextView) convertView.findViewById(R.id.phone_tv);            convertView.setTag(viewHolder);        }else {            viewHolder = (ViewHolder) convertView.getTag();        }        Bean bean = mDatas.get(position);        viewHolder.mTitle.setText(bean.getTitle());        viewHolder.mDesc.setText(bean.getDesc());        viewHolder.mTime.setText(bean.getTime());        viewHolder.mPhoneNumber.setText(bean.getPhoneNumber());        return convertView;    }    private class ViewHolder{        TextView mTitle;        TextView mDesc;        TextView mTime;        TextView mPhoneNumber;    }}

还需要写一个item_listview.xml:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:padding="10dp"    android:layout_width="match_parent"    android:layout_height="wrap_content">    <RelativeLayout        android:id="@+id/rl"        android:layout_width="match_parent"        android:layout_height="match_parent">        <TextView            android:id="@+id/title_tv"            android:textSize="18sp"            android:textColor="#000000"            android:layout_width="wrap_content"            android:layout_height="wrap_content" />        <TextView            android:id="@+id/desc_tv"            android:textSize="15sp"            android:layout_below="@id/title_tv"            android:layout_width="wrap_content"            android:layout_height="wrap_content" />        <TextView            android:id="@+id/time_tv"            android:textSize="12sp"            android:paddingTop="5dp"            android:textColor="#890909"            android:layout_below="@id/desc_tv"            android:layout_width="wrap_content"            android:layout_height="wrap_content" />    </RelativeLayout>    <RelativeLayout        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignParentRight="true"        android:layout_alignParentBottom="true">        <ImageView            android:id="@+id/imageview"            android:src="@drawable/phone"            android:layout_width="20dp"            android:layout_height="30dp" />        <TextView            android:id="@+id/phone_tv"            android:textColor="#034ef1"            android:layout_toRightOf="@id/imageview"            android:layout_centerHorizontal="true"            android:layout_centerVertical="true"            android:layout_width="wrap_content"            android:layout_height="wrap_content" />    </RelativeLayout></RelativeLayout>

如果是开发一个简单点的APP用到的ListView数量不会太多,我们只要去写几个BaseAdapter实现类就可以了。但如果有有几十个ListView,此时的你该怎么办?每个ListView都去写一个适配的Adatper类吗?当然你如果不嫌累的话也不是不可以,但如果有办法可以让自己减少很多工作量,避免做重复无意义劳动,何乐而不为呢?

三、打造ListView的万能适配器

万能适配器思想:使用模板方法设计模式其核心思想很简单:抽取重复代码!

我们在继承BaseAdapter类时,都需要去实现它里面的抽象方法(getCount, getItem, getItemId, getView),其中除了getView这个方法里需要实现的代码不同,其他的都基本一样。而这个getView方法里,我们考虑到性能的问题,我们经常会引入一个ViewHolder类,尽可能的去节省资源。那么解决问题的思路就出来了,可以把这个适配器抽取成两部分:
第一部分是解决(getCount, getItem, getItemId)方法里重复代码的问题;
第二部分是分离getView方法里使用到的ViewHolder,把它单独抽取出来成一个独立的类,利用键值对Key=>Value的方法,以控件ID去寻找对应的View对象。

仔细观察上面的传统的MyAdapter写法,getCount(), getItem(), getItemId()这三个方法都是一样,我们可以全部抽取出来。于是,可以写一个泛型使其变成一个抽象的基类,继承自BaseAdapter,然后该子类只需要去关心getView方法就行了:

package com.wz.adapterdemo;import android.content.Context;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import java.util.List;public abstract class CommonAdapter<T> extends BaseAdapter {    private Context context;    private List<T> list;    private int layoutId;    public CommonAdapter(Context context, List<T> list,int layoutId) {        this.context = context;        this.list = list;        this.layoutId = layoutId;    }    @Override    public int getCount() {        return list == null ? 0:list.size();    }    @Override    public T getItem(int position) {        return list.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ......    }}

好,实现了前面三个方法,我们再来看看getView方法,在该方法里面要先判断ViewHolder是否为空,为空则去Inflate一个xml文件进来,再绑定下视图,设置一个Tag,不为空的时候直接引用Tag。

现在,我们要把ViewHolder提取出来,但每一个item都和ViewHolder相关联,而item是需要我们自己定义的,于是,我们可以把item作为参数传入,对于item里面的控件,因为每一个控件都对应这一个id,所以可以用键值对处理,我们会先想到HashMap,但是在Android中已经为我们提供了一个性能更好的数据结构来代替HashMap,那就是SparseArray。于是,ViewHolder可以这样写:

package com.wz.adapterdemo;import android.content.Context;import android.util.SparseArray;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import android.widget.TextView;public class CommonViewHolder {    private SparseArray<View> views;    private int postion;    private View contertView;    private Context context;    /**     * 构造方法,完成传统Adapter里的创建convertView对象     */    private CommonViewHolder(Context context, View contertView,ViewGroup parent,                             int lagoutId, int postion) {        this.context = context;        this.views = new SparseArray<>();        this.contertView = LayoutInflater.from(context).inflate(lagoutId,parent,false);        this.postion = postion;        this.contertView.setTag(this);    }    /**     * 入口方法,完成传统Adapter里面实例化ViewHolder对象工作     */    public static CommonViewHolder getCommonViewHolder(Context context, View convertView, int layoutId,                                                       ViewGroup parent, int position){        if(convertView == null){            return new CommonViewHolder(context,convertView,parent,layoutId,position);        }else {            CommonViewHolder commonViewHolder = (CommonViewHolder)convertView.getTag();            /*由于ListView的复用,比如屏幕只显示5个Item,那么当下拉到第6个时会复用第1个的Item             *所以这边需要更新position*/            commonViewHolder.postion = position;            return commonViewHolder;        }    }    /**     * 根据控件Id获取对应View对象     */    public  <T extends View> T getView(int viewId){        View view = views.get(viewId);        if(view == null){            view = contertView.findViewById(viewId);            views.put(viewId,view);        }        return (T)view;    }    /**     * 返回设置好的ConvertView对象     */    public View getContertView(){        return contertView;    }    /**     *给TextView设置字符串     */    public CommonViewHolder setText(int viewId,String text){        TextView textView = getView(viewId);        textView.setText(text);        return this;    }    /**     *给ImageView设置图片资源     */    public CommonViewHolder setImageResource(int viewId,int drawableId){        ImageView imageView = getView(viewId);        imageView.setImageResource(drawableId);        return this;    }}

这里我们提供了一个入口方法getCommonViewHolder来得到一个ViewHolder的实例对象,若实例不存在,我们去创建并设置Tag保存,这点和先前的ViewHolder所做的事情是一样的。由于所有的控件都是View的子类,这里提供了一个getView来获取各控件的对象,在我们需要使用的时候强转成我们所需要的控件类型就可以了。

于是,我们CommonAdapter中的getView方法可以这么写:

    @Override    public View getView(int position, View convertView, ViewGroup parent) {        CommonViewHolder commonViewHolder = CommonViewHolder.getCommonViewHolder(context,convertView,layoutId,parent,position);        convert(commonViewHolder,getItem(position));        return commonViewHolder.getContertView();    }    public abstract void convert(CommonViewHolder holder,T item);

这里提供一个抽象方法convert,在调用的地方用户自己实现。

最后我们来看主方法的调用(用户根据需要重载convert方法):

package com.wz.adapterdemo;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.widget.ListView;import com.wz.adapterdemo.bean.Bean;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity {    private ListView listView;    private List<Bean> datas;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        listView = (ListView) findViewById(R.id.listView);        initDatas();       // listView.setAdapter(new MyAdapter(this,datas));        listView.setAdapter(new CommonAdapter<Bean>(this,datas,R.layout.item_listview) {            @Override            public void convert(CommonViewHolder holder, Bean item) {                  holder.setText(R.id.title_tv,item.getTitle())                        .setText(R.id.desc_tv,item.getDesc())                        .setText(R.id.time_tv,item.getTime())                        .setText(R.id.phone_tv,item.getPhoneNumber())                        .setImageResource(R.id.imageview,R.drawable.phone);            }        });    }    private void initDatas(){        datas = new ArrayList<>();        Bean bean1 = new Bean("移动开发1","Android手机开发1","2016-11-02","000-123456");        datas.add(bean1);        Bean bean2 = new Bean("移动开发2","Android手机开发2","2016-11-02","000-123456");        datas.add(bean2);        Bean bean3 = new Bean("移动开发3","Android手机开发3","2016-11-02","000-123456");        datas.add(bean3);        Bean bean4 = new Bean("移动开发4","Android手机开发4","2016-11-02","000-123456");        datas.add(bean4);        Bean bean5 = new Bean("移动开发5","Android手机开发5","2016-11-02","000-123456");        datas.add(bean5);    }}

很明显,代码量减少很多,CommonAdapter和CommonViewHolder再也不需要修改,需要什么我们往里面直接加就可以了,这样让我们可以更为专注的去实现核心代码。当然还可以更简化一点,把这些ViewHolder.getView和setText,setImage等方法再一次封装,变成只传递控件Id和对应数据就够了,这样一来我们连类都不需要写了。于是,我们可以再定义一个Adapter继承CommonAdapter,在里面实现控件内容的设定:

package com.wz.adapterdemo;import android.content.Context;import com.wz.adapterdemo.bean.Bean;import java.util.List;public class MyCommonAdapter extends CommonAdapter<Bean> {    public MyCommonAdapter(Context context, List<Bean> list, int layoutId) {        super(context, list, layoutId);    }    @Override    public void convert(CommonViewHolder holder, Bean item) {        holder.setText(R.id.title_tv,item.getTitle())                .setText(R.id.desc_tv,item.getDesc())                .setText(R.id.time_tv,item.getTime())                .setText(R.id.phone_tv,item.getPhoneNumber())                .setImageResource(R.id.imageview,R.drawable.phone);    }}

在MainActivity中setAdapter:

 listView.setAdapter(new MyCommonAdapter(this,datas,R.layout.item_listview));

这样,以后如果需要使用适配器Adapter就不需要再去继承BaseAdapter了,直接继承CommonAdapter配合CommonViewHolder就可以了。

项目案例源码下载地址:
http://download.csdn.net/detail/wei_zhi/9671511

1 0
原创粉丝点击