Android自定义适配器Adapter浅析

在android日常开发中,我们用到最多的就是加载数据,尤其是适配器,Adapter是连接后端数据和前端显示的适配器接口。如下图直观的表达了Data、Adapter、View三者的关系:
这里写图片描述

这里写图片描述

1、我刚刚接触android的时候的做法常常是使用Android自带的simpleAdapter,simpleAdapter的扩展性好,以listview为例子。常规的做法会在主布局写一个listview,然后你写一个item布局,可以定义各种各样的布局出来,可以放上ImageView,还可以放上Button等等。那时候,我认为满足了我日常开发的所有需求,嘿嘿

item.xml布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:textColor="#ffffff"
android:textSize="20sp"
/>
</LinearLayout>

Activity中的代码:

public class SimpleAdapterActivity extends Activity {
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
          //map.put(键,值)
         List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
         Map<String, Object> map = new HashMap<String, Object>();
    for (int i = 0; i < 3; i++) {
        map.put("title", "苹果"+i);
         map.put("img", R.drawable.icon);
         list.add(map);
        }
       SimpleAdapter adapter = new SimpleAdapter(this, list, R.layout.simple, new String[] { "title",  "img" }, new int[] { R.id.title, R.id.img });
 //参数依次是Context context, List<? extends Map<String, ?>>     //data,int resource, String[] from, int[] to
         你的listview.setAdapter(adapter);
     }
 }

总结:这种方法,我使用了很久,那时候啥也不懂,总觉得自定义adapter真的是好遥远的事情。在学校里比赛的时候,遇到一个问题,要在item.xml布局中加一个Ratingbar,类似做一个点评的操作,当时就抓狂了,各种百度自定义适配器,总是显示不出来Ratingbar中的数值。。。心酸呐,所以我打算对自定义adapter做一点点尝试。。。

2、继承baseAdapter,打造自定义适配器

adapter中的方法全透析:

package adapter;

import java.util.List;
import java.util.Map;
import com.example.adaptertest.R;
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 TestAdapter extends BaseAdapter {

    private List<String> mData;
    protected Context mContext;
    private int layoutId;

    // 带参数的构造方法
    public TestAdapter(Context context, List<String> data, int layoutId) {
        this.mData = data; // 传入的是item集合的数据
        this.mContext = context; // 传入的是当前的activity.this(上下文)
        this.layoutId = layoutId; // 传入的是item子布局
    }

    // 获取当前子项的总数
    @Override
    public int getCount() {
        return mData.size(); // 返回传入数据List的总数
    }

    // 获取选中位置的item数值
    @Override
    public Object getItem(int position) {
        return mData.get(position); // 当前位置(position)数据
    }

    // 获取选中位置
    @Override
    public long getItemId(int position) {
        return position;
    }

    // 获取当前子项并逐一进行显示。当执行操作的时候通过position进行对当前项的操作
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // 动态加载item的布局
        convertView = LayoutInflater.from(mContext).inflate(layoutId, null);
        TextView tv = (TextView) convertView.findViewById(R.id.checkBtn);
        String itemData = mData.get(position);// 获取当前项的数值
        tv.setText(itemData);// 赋值
//tv.setOnClickListener(l) 当然如果子view中想监听一些方法也写在这里
        return convertView; // 返回的是加载数据后的view
    }

}

总结:当然你会了这些后你就可以自定义自己的适配器了。但是有木有遇到过当数据很多很多的时候,往下滑ListView时有时候会卡顿,这就需要我们来优化它了。而且往后的日子里,如果你的listview不幸有imageview的话,而且你也没做缓存还会oom。。。一路走过来都是坑有木有

3、convertView重用,ViewHolder的子View复用(常规的写法)

adapter中的代码:

package adapter;

import java.util.List;  

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

public class MyAdapter extends BaseAdapter  
{  

    private Context mContext;  
    private List<String> mDatas; 
    private int layoutId; 

    public MyAdapter(Context context, List<String> mDatas,int layoutId)  
    {  
        this.layoutId= layoutId;  
        this.mContext = context;  
        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;  
    }  

    @Override  
    public View getView(int position, View convertView, ViewGroup parent)  
    {  
        ViewHolder viewHolder = null;  
     //这样做的好处是仅仅第一次getview的时候会动态加载item的布局convertView,而且不用只有第一次才需要findviewbyId了,因为getview的方法是每个item都会执行,所以我们把item中的所有控件放到容器viewHolder里面了,然后再把它放到convertview的tag里面
        if (convertView == null)  
        {  
            convertView = LayoutInflater.from(mContext).inflate(layoutId, null);
            viewHolder = new ViewHolder();  
            viewHolder.mTextView = (TextView) convertView  
                    .findViewById(R.id.tv_title);  
            convertView.setTag(viewHolder);  
        } else  
        {  
            viewHolder = (ViewHolder) convertView.getTag();  
        }  
       //重用子view控件对象
       viewHolder.mTextView.setText(mDatas.get(position));  
        return convertView;  
    }  

  //相当于容器
    private final class ViewHolder  
    {  
        TextView mTextView;  
    }  

}  

总结:ListView的原理:ListView中的每一个Item显示都需要Adapter调用一次getView()的方法,这个方法会传入一个convertView的参数,这个方法返回的View就是这个Item显示的View。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存空间,即创建View对象(mInflater.inflate(R.layout.lv_item, null);从xml中生成View,这是属于IO操作)是耗时操作,所以必将影响性能。Android提供了一个叫做Recycler(反复循环)的构件,就是当ListView的Item从滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从生成一个Item,而此时调用的getView中的convertView参数就是滚出屏幕的缓存Item的View,所以说如果能重用这个convertView,就会大大改善性能。
我们都知道在getView()方法中的操作是这样的:先从xml中创建view对象(inflate操作,我们采用了重用convertView方法优化),然后在这个view去findViewById,找到每一个item的子View的控件对象,如:ImageView、TextView等。这里的findViewById操作是一个树查找过程,也是一个耗时的操作,所以这里也需要优化,就是使用ViewHolder,把每一个item的子View控件对象都放在Holder中,当第一次创建convertView对象时,便把这些item的子View控件对象findViewById实例化出来并保存到ViewHolder对象中。然后用convertView的setTag将viewHolder对象设置到Tag中, 当以后加载ListView的item时便可以直接从Tag中取出复用ViewHolder对象中的,不需要再findViewById找item的子控件对象了。这样便大大提高了性能。
以上两段话说到我心坎里了,就借鉴过来了。。。嘿嘿

4、使用鸿老师的万能适配器。。。

首先分析下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.

package adapter;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.Html.ImageGetter;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;

public class ViewHolder {
    private SparseArray<View> store;
    private int mPosition;
    private View mContentView;
    private Context mContext;

    public ViewHolder(Context context, int layoutId, int position,
            ViewGroup parent) {
        store = new SparseArray<View>();
        mContext = context;
        mPosition = position;
        mContentView = LayoutInflater.from(context).inflate(layoutId, parent,
                false);
        mContentView.setTag(this);
    }

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

    /**
     * 获取
     */
    @SuppressWarnings("unchecked")
    public <T extends View> T getView(int id) {
        View view = store.get(id);
        if (null == view) {
            view = mContentView.findViewById(id);
            store.put(id, view);
        }
        return (T) view;
    }

    public View getContentView() {
        return mContentView;
    }

    public Context getContext() {
        return mContext;
    }

    public ViewHolder setImage(int id, Bitmap bm) {
        ImageView iv = getView(id);
        iv.setImageBitmap(bm);
        return this;
    }

    public ViewHolder setImage(int id, int resId) {
        ImageView iv = getView(id);
        iv.setImageDrawable(mContext.getResources().getDrawable(resId));
        return this;
    }

    // // 传递URL
    // public ViewHolder setImage(int id, String url) {
    // ImageView iv = getView(id);
    // // BitmapDisplayConfig a = new BitmapDisplayConfig();
    // // // 设置图片的大小
    // // a.setBitmapMaxSize(new BitmapSize(150, 150));
    // // 设置未加载时候的图片
    // BitmapUtils bitmapUtils = new BitmapUtils(mContext);
    // bitmapUtils.configDefaultLoadingImage(R.drawable.empty);
    // bitmapUtils.display(iv, url);
    // // new BitmapUtils(mContext).display(iv, url);
    // return this;
    // }
    public ViewHolder setText(int id, String text) {
        TextView tv = getView(id);
        tv.setText(text);
        return this;
    }

    // public ViewHolder setTextWithdrable(int id, int drawableid, String text)
    // {
    // TextView tv = getView(id);
    // Drawable drawable = mContext.getResources().getDrawable(drawableid);
    // tv.setCompoundDrawables(drawable, null, null, null);
    // drawable.setBounds(0, 0, drawable.getMinimumWidth(),
    // drawable.getMinimumHeight());// 必须设置图片大小,否则不显示
    // tv.setText(text);
    // return this;
    // }

    // boolean值
    public ViewHolder setBoolean(int id, Boolean bool) {
        CheckBox chek = getView(id);
        chek.setChecked(bool);
        return this;
    }

    public ViewHolder setTextFromHtml(int id, String text) {
        TextView tv = getView(id);
        tv.setText(Html.fromHtml(text));
        return this;
    }

    public ViewHolder setTextFromHtmlWithimg(int id, String text,
            String strImage, ImageGetter ig) {
        TextView tv = getView(id);
        tv.setText(Html.fromHtml(strImage, ig, null));
        tv.append(Html.fromHtml(text));
        return this;
    }

    public int getPosition() {
        return mPosition;
    }

}

打造通用的Adapter:

Adapter一般需要保持一个List对象,存储一个Bean的集合,不同的ListView,Bean肯定是不同的,这个CommonAdapter肯定需要支持泛型,内部维持一个List,就解决我们的问题了;

package adapter;

import java.util.List;

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

public abstract class AbListAdapter<T> extends BaseAdapter {
    private List<T> mData;
    protected Context mContext;
    private int layoutId;

    public AbListAdapter(Context context, List<T> data, int layoutId) {
        this.mData = data;
        this.mContext = context;
        this.layoutId = layoutId;
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public T 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 = ViewHolder.get(mContext, layoutId, convertView,
                position, parent);
        converView(viewHolder, mData.get(position));
        return viewHolder.getContentView();
    }

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

    public List<T> getData() {
        return mData;
    }
}

现在就在activity中你只要这么写了。。。。

mAdapter = new CommonAdapter<String>(getApplicationContext(),  
                R.layout.item_single_str, mDatas)  
        {  
            @Override  
            protected void convert(ViewHolder viewHolder, String item)  
            {  
                viewHolder.setText(R.id.id_tv_title, item);  
            }  
        };  

convertView里面只要一行代码了~~~

声明:最后一步通用适配器是鸿洋大神写的博客:http://blog.csdn.net/lmj623565791/article/details/38902805

总结:个人觉得日常开发中懂得了如何自定义adapter后,第三种方法后,如果碰到要设置imageview的时候,可以采用volley框架或者xutils里面的BitmapUtils,里面自带了缓存机制,就不会在快速滑动过程中报oom了。。。。好了,不说了,我要继续跳坑了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值