Android之打造ListView的万能适配器

一、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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值