Adapter适配器之瘦身之路

参考文章:
Android 快速开发系列 打造万能的ListView GridView 适配器
首先,ListView在应用中是经常用到的控件,在代码中会存在大量的Adapter。因为在代码中实现一个数据适配器只需要继承BaseAdapter,实现其中和个方法即可,使用起来很简单。所以在开发中就没有想着去做封装。不知不觉这类相同的代码却占具相当大的代码量,也会使代码的可读性变差。
看看自定义一个MyAdapter类需要做一些什么:

package com.loudz.myandroid.extend;

import java.util.List;

import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public class MyAdapter extends BaseAdapter {
    protected List mDatas;

    public MyAdapter(List mDatas) {
        super();
        this.mDatas = mDatas;
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return mDatas.size();
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return mDatas.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
        return null;
    }

}

纵观Adapter实现,我们最关注的 是`getView(…)方法,在这里设置每个Item的显示内容,及部分控件的事件等。
首先把除getView方法外的其他方法提到父类中去,创建一个CommonAdapter作为父类,为了支持所有的数据显示,CommonAdapter使用泛型。实现代码如下:

package com.loudz.myandroid.extend;

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public abstract class CommonAdapter<T> extends BaseAdapter {
    protected List mDatas;

    private <T> CommonAdapter() {
    }

    public <T> CommonAdapter(List<T> mDatas) {
        this.mDatas = mDatas;
    }

    @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;
    }

    protected abstract View getView(int position, View convertView, ViewGroup parent);
}

有了CommonAdapter类,我们来创建自己的Adapter,此时我们只需要实现getView(…)方法即可。只看getView实现

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Holder holder ;
        if (convertView != null) {
            holder = new ViewHolder();
            convertView = layoutInflater.inflate(R.layout.activity_category_item, null);
            holder.textView = (TextView) convertView.findViewById(R.id.title);
            holder.imageView = (ImageView) convertView.findViewById(R.id.image);
            convertView.setTag(holder);
        } else {
            holder = (Holder ) convertView.getTag();
        }
        holder.textView.setText(mData.get(position).get("title").toString());
                    holder.imageView.setBackgroundResource(Integer.valueOf(mData.get(position).get("image").toString()));
            }
        return convertView;
    }
    //内部类
    class Holder {
          private TextView textView;
          private ImageView imageView;
   }

虽然只需要实现getView方法,但方法内部的代码还是比较繁琐,应该还有提炼的空间。分析其中代码,getView方法内部代码结构基本都这架势。1、判断convertView是否存在,不存在创建一个,并创建一个holder,2、若存在获取holer,3、设置值。理论上以上4步中前三步都可以提到父类中去,子类只需要设置Item值,如果这样子类实现就简便多了,先来看看把1步提到父类来完成,修改CommonAdapter的getView方法,修改结果如下:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            //mContext,当前Activity,在CommonAdapter构造方法中传递进来
            //mItemLayoutId,Item所使用的布局文件resId。构造方法中传递是来
            //判断convertView不存在立即创建一个。
            convertView = LayoutInflater.from(mContext).inflate(mItemLayoutId, null);
        }
        //convert方法是一个抽象方法,需要由子类去实现。
        convert(position, convertView, parent);
        return convertView;
    }

    protected abstract void convert(int position, View convertView, ViewGroup parent);

上面修改将创建convertView操作提到父类中,来看看现在使用时的效果,子类的实现使用匿名内部类了。

    //匿名内部类实现ListAdapter,
        listview.setAdapter(listViewAdapter = new CommonAdapter<Map<String, String>>(this, mData, R.layout.list_item) {
        //内部创建了一个Holder类,用于存放item中交互控件.
            class Holder {
                private TextView textView;
                private ImageView imageView;
            }
            //实现convert方法,
            @Override
            protected void convert(int position, View convertView, ViewGroup parent) {
                //这里的convertView已经在父类创建好了,直接获取holder,若不存在需要获取item控件,若存在直接就设置值就行了。
                Holder holder = (Holder) convertView.getTag();
                if (holder == null) {
                    holder = new Holder();
                    holder.textView = (TextView) convertView.findViewById(R.id.title);
                    holder.imageView = (ImageView) convertView.findViewById(R.id.image);
                    convertView.setTag(holder);
                }
                holder.textView.setText(mData.get(position).get("title").toString());
                    holder.imageView.setBackgroundResource(Integer.valueOf(mData.get(position).get("image").toString()));
            }

        });

封装到这里,还是看到了一定的效果。再来看看如何把前面提到的第2步也让父类来完成,要重写它,还得先了解它。

这里首先为看看Holder这个类。每次使用Adapter时都要定义一个Holder,之前也没想过为什么。那么为什么非得定义一个Holder呢?那假如不定义Holder,每次getView的时候都会从convertView中通过findV iewById去获取控件,那问题来了,每次都重新获取控件真的好吗?费时费内存。好了,我找到Holder存在的意义了:Adapter的设计本身就是有对象重用,尽可能的节省内存消耗,所以,Holderg还是很有存在的必要的,虽然不能舍弃,但我们还可以对它进行提炼。Holder的作用无非就是把我们用到的Item的控件保存起来,下次可直接使用,传统的方法是把我们要用的控件对象事先定义为Holder的成员变量,第一次创建时赋值,以后使用时可以直接引用。说白了Holder就是Item控件的容器,他实现容器的方法就是定义成员变量。只要改变一下容器的实现方式,达到任何item的控件都可以丢进去,然后通过resId来进行检索出来,这样Holder就可以通用了,使用Map就可以达到目的。

好了,来创建我们的Holder类,Holder内部申明一个Map<Integer,View>mView。第一次获取ItemView时直接put到mView中。使用时根据resId就可以获取出来了。把这个类名定义ViewHolder,实现中下:

package com.loudz.myandroid.extend;

import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;

//ViewHoler内部定义了一个Map用于存放控件和convertView用于查找对象
public class ViewHolder2 {
    //android自身实现一个比Map更好类
    //private SparseArray<View> mViews;可以用这个类替换map
    private Map<Integer, View> mViews;

    private View convertView;

    private ViewHolder2() {
    }

    public ViewHolder2(View convertView, ViewGroup parent) {
        this.convertView = convertView;
        mViews = new HashMap<>();
    }

    //获取对象时首先从mViews中获取,如果有直接返回,如果没有才通过findViewById查找,查找到后添加到mViews中。这样达到复用的目的。
    @SuppressWarnings("unchecked")
    public <T> T getView(int viewId, Class<T> clazz) {
        View view = mViews.get(viewId);
        if (view == null) {
            view = convertView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return (T) view;
    }

    //在等号赋值地无需要强转
    public <T extends View> T getView(int viewId) {
        View view = mViews.get(viewId);
        if (view == null) {
            view = convertView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return (T) view;
    }
}

已经定义了一个ViewHolder了,那么创建ViewHolder的工作就交给父类去做了,CommonAdapter类getView的调整,添加了一句代码。

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(mContext).inflate(mItemLayoutId, null);
            //创建holer并设置convertView的tag.
            ViewHolder2 holder = new ViewHolder2(convertView, parent);
            convertView.setTag(holder);
        }
        convert(position, convertView, parent);
        return convertView;
    }

再看使用效果

        listview.setAdapter(listViewAdapter = new CommonAdapter<Map<String, String>>(this, mData, R.layout.list_item) {

            @Override
            protected void convert(int position, View convertView, ViewGroup parent) {
                //父类创建,直接获取
                //说好的,把holder的获取也交由父类来完成的呢。文章还没完,在下面呢。
                ViewHolder2 holder = (ViewHolder2) convertView.getTag();
                //getView获取控件后直接setText,没有强转,使用方便
                holder.getView(R.id.title, TextView.class).setText(mData.get(position).get("title").toString());
                holder.getView(R.id.image, ImageView.class).setBackgroundResource(Integer.valueOf(mData.get(position).get("image").toString()));
                //等号赋值地也无需要强转。
                TextView tv = holder.getView(R.id.title);
            }

        });

到这里,代码又简洁了好了,经过几次修改以后,发现子类继承的这个方法中参数已经不再实用了,holder可以直接由方法传进来,convertView就不需要了,另外可将position的值对象传进方法中来,再次修改CommonAdapter类的getView方法和抽象方法convert的参数:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if (convertView == null) {
            convertView = LayoutInflater.from(mContext).inflate(mItemLayoutId, null);
            ViewHolder2 holder = new ViewHolder2(convertView, parent);
            convertView.setTag(holder);
        }
        convert((ViewHolder2) convertView.getTag(), (T) mDatas.get(position), parent);
        return convertView;
    }
    protected abstract void convert(ViewHolder2 holder, T bean, ViewGroup parent);

最终我们使用适配器的代码如下,还是匿名内部类走起。

listview.setAdapter(listViewAdapter = new CommonAdapter<Map<String, String>>(this, mData, R.layout.list_item) {

            @Override
            protected void convert(ViewHolder2 holder, Map<String, String> bean, ViewGroup parent) {
                //这里没有将position和convertView传递进来,一般的赋值基本上用不到了。但如果需要用到这两个参数怎么办,我们可以在holder对象中保存这两个参数的引用,这样就走遍天下都不怕了。
                holder.getView(R.id.title, TextView.class).setText(bean.get("title").toString());
                holder.getView(R.id.image, ImageView.class).setBackgroundResource(Integer.valueOf(bean.get("image").toString()));
                TextView tv = holder.getView(R.id.title);
            }
        });

Adapter的封装到此结束,把最终完整的代码帖出来了。

CommonAdapter类

package com.loudz.myandroid.extend;

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public abstract class CommonAdapter<T> extends BaseAdapter {

    protected Context mContext;

    protected List mDatas;

    protected LayoutInflater mInflater;

    protected int mItemLayoutId;

    private <T> CommonAdapter() {

    }

    public <T> CommonAdapter(Context context, List<T> mDatas, int itemLayoutId) {
        mInflater = LayoutInflater.from(context);
        this.mDatas = mDatas;
        mContext = context;
        mItemLayoutId = itemLayoutId;
    }

    @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 = ViewHolder.get(mContext, position, convertView, parent, mItemLayoutId);
        convert(viewHolder, (T) mDatas.get(position));
        return viewHolder.getConvertView();
    }

    protected abstract void convert(ViewHolder viewHolder, T bean);

}

ViewHoler类

package com.loudz.myandroid.extend;

import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class ViewHolder {

    // convertView中包含的控件,第一镒用到后就把引用保存起来,下次用到时就不需要findbyid了,提高了效率。
    private SparseArray<View> mViews;

    private View convertView;

    private int position;

    private ViewHolder() {
    }

    public ViewHolder(Context context, int position, View convertView, ViewGroup parent, int layoutId) {
        this.convertView = convertView;
        this.position = position;
        mViews = new SparseArray<View>();
    }

    public static ViewHolder get(Context context, int position, View convertView, ViewGroup parent, int layoutId) {
        if (convertView == null) {
            convertView = LayoutInflater.from(context).inflate(layoutId, null);
            convertView.setTag(new ViewHolder(context, position, convertView, parent, layoutId));
        }
        return (ViewHolder) convertView.getTag();
    }

    public View getConvertView() {
        return this.convertView;
    }

    public int getPosition() {
        return this.position;
    }

    @SuppressWarnings("unchecked")
    public <T> T getView(int viewId, Class<T> clazz) {
        View view = mViews.get(viewId);
        if (view == null) {
            view = convertView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return (T) view;
    }

    public <T extends View> View getView(int viewId) {
        return convertView.findViewById(viewId);
    }
}

点击下载源码

下面是一个简单的示例代码,展示如何创建一个自定义适配器(Adapter)来填充ListView的数据: ```java public class MyAdapter extends BaseAdapter { private Context mContext; private List<String> mData; public MyAdapter(Context context, List<String> data) { mContext = context; mData = data; } @Override public int getCount() { return mData.size(); } @Override public Object getItem(int position) { return mData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { // 如果convertView为空,说明还没有被实例化,需要创建ViewHolder对象,并将其与布局文件进行绑定 convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false); viewHolder = new ViewHolder(); viewHolder.textView = convertView.findViewById(R.id.text_view); convertView.setTag(viewHolder); } else { // 如果convertView不为空,则直接从convertView中获取ViewHolder对象 viewHolder = (ViewHolder) convertView.getTag(); } // 设置数据 String item = mData.get(position); viewHolder.textView.setText(item); return convertView; } // ViewHolder类,用于缓存控件实例,避免重复查找 private static class ViewHolder { TextView textView; } } ``` 在上述代码中,首先定义了一个自定义适配器`MyAdapter`,该适配器继承自`BaseAdapter`。在构造方法中传入上下文和数据源,然后实现了必要的方法,包括`getCount()`、`getItem()`、`getItemId()`和`getView()`。 在`getView()`方法中,首先判断convertView是否为null,如果为null则说明需要创建新的布局实例,并将其与ViewHolder对象进行绑定。然后通过ViewHolder对象获取布局中的控件实例,并设置数据。 最后,可以使用`setAdapter()`方法将自定义适配器与ListView进行绑定: ```java ListView listView = findViewById(R.id.list_view); MyAdapter adapter = new MyAdapter(this, dataList); listView.setAdapter(adapter); ``` 其中,`dataList`是一个包含要显示的数据的List集合。通过调用`setAdapter()`方法,将自定义适配器与ListView绑定,即可实现数据填充。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wolf犭良

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值