Android万能适配器改进篇--ListView,GridView适用

本文介绍了一种改进的Android ListView适配器,该适配器支持复杂的多重行布局,通过重写BaseAdapter的方法实现了根据不同数据项显示不同布局的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于ListView相信大家都很熟悉,在这个RecycleView盛行的年代现在再谈ListView似乎有点Out,
但是作为Android老牌控件,在一些要求不那么高的情况下,
ListView还是有可取之道的(好歹实现比RecycleView简单点)。
传统的优化复用convertView,ViewHolder保存控件索引等等,大家都很熟悉了,
这里就不多说,再进一步超级好用的万能适配器相信很多人也很熟悉了,
对于不熟悉的童鞋,
请移驾鸿洋大神的博客
Android 快速开发系列 打造万能的ListView GridView 适配器.

 先贴上大神代码:
public class ViewHolder  
{  
    private final SparseArray<View> mViews;  
    private int mPosition;  
    private View mConvertView;  

    private ViewHolder(Context context, ViewGroup parent, int layoutId,  
            int position)  
    {  
        this.mPosition = position;  
        this.mViews = new SparseArray<View>();  
        mConvertView = LayoutInflater.from(context).inflate(layoutId, parent,  
                false);  
        // setTag  
        mConvertView.setTag(this);  
    }  

    /** 
     * 拿到一个ViewHolder对象 
     *  
     * @param context 
     * @param convertView 
     * @param parent 
     * @param layoutId 
     * @param position 
     * @return 
     */  
    public static ViewHolder get(Context context, View convertView,  
            ViewGroup parent, int layoutId, int position)  
    {  
        if (convertView == null)  
        {  
            return new ViewHolder(context, parent, layoutId, position);  
        }  
        return (ViewHolder) convertView.getTag();  
    }  

    public View getConvertView()  
    {  
        return mConvertView;  
    }  

    /** 
     * 通过控件的Id获取对于的控件,如果没有则加入views 
     *  
     * @param viewId 
     * @return 
     */  
    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;  
    }  

    /** 
     * 为TextView设置字符串 
     *  
     * @param viewId 
     * @param text 
     * @return 
     */  
    public ViewHolder setText(int viewId, String text)  
    {  
        TextView view = getView(viewId);  
        view.setText(text);  
        return this;  
    }  

    /** 
     * 为ImageView设置图片 
     *  
     * @param viewId 
     * @param drawableId 
     * @return 
     */  
    public ViewHolder setImageResource(int viewId, int drawableId)  
    {  
        ImageView view = getView(viewId);  
        view.setImageResource(drawableId);  

        return this;  
    }  

    /** 
     * 为ImageView设置图片 
     *  
     * @param viewId 
     * @param drawableId 
     * @return 
     */  
    public ViewHolder setImageBitmap(int viewId, Bitmap bm)  
    {  
        ImageView view = getView(viewId);  
        view.setImageBitmap(bm);  
        return this;  
    }  

    /** 
     * 为ImageView设置图片 
     *  
     * @param viewId 
     * @param drawableId 
     * @return 
     */  
    public ViewHolder setImageByUrl(int viewId, String url)  
    {  
        ImageLoader.getInstance(3, Type.LIFO).loadImage(url,  
                (ImageView) getView(viewId));  
        return this;  
    }  

    public int getPosition()  
    {  
        return mPosition;  
    }  

}  
public abstract class CommonAdapter<T> extends BaseAdapter  
{  
    protected LayoutInflater mInflater;  
    protected Context mContext;  
    protected List<T> mDatas;  
    protected final int mItemLayoutId;  

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

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

    @Override  
    public T 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)  
    {  
        final ViewHolder viewHolder = getViewHolder(position, convertView,  
                parent);  
        convert(viewHolder, getItem(position));  
        return viewHolder.getConvertView();  

    }  

    public abstract void convert(ViewHolder helper, T item);  

    private ViewHolder getViewHolder(int position, View convertView,  
            ViewGroup parent)  
    {  
        return ViewHolder.get(mContext, convertView, parent, mItemLayoutId,  
                position);  
    }  

} 
 但是这个这个万能适配器对于实现多重行布局还是有点欠缺的,网上也少有这种文章,今天我就自己小小改进了这个适配器以支持实现复杂行布局。
 首先,多重行布局实现我们需要重写BaseAdapter的方法:
//返回多重行布局的样式数量
getViewTypeCount()
//返回行布局类型,取值是0~样式数量-1
getItemViewType(int position)
之前万能适配器构造方法:
 /**
     * 实现单独行布局构造方法
     *
     * @param context    上下文
     * @param datas      数据源
     * @param itemlayout 布局ID
     */
    public CommonAdapter(Context context, List<T> datas, int itemlayout) {
        this.mContext = context;
        this.mDatas = datas;
        this.mItemlayoutID = itemlayout;
    }
 只传入一个布局itemlayout
 而我们需要实现多重样式那就传入样式的数组嘛
 多写一个构造方法
  /**
     * 实现复杂行布局构造方法
     *
     * @param context 上下文
     * @param datas   数据源
     * @param layouts 复杂行布局ID数组
     */
    public CommonAdapter(Context context, List<T> datas, int[] layouts) {
        this.mContext = context;
        this.mlayoutIDs = layouts;
        this.mDatas = datas;
        //决定是否是多重行布局
        this.isMoreType = true;
    }

之后对于重写的两个方法我们就需要这样来实现:

 /**
     * 返回多重布局数量
     *
     * @return
     */
    @Override
    public int getViewTypeCount() {
        return isMoreType ? mlayoutIDs.length :
                super.getViewTypeCount();
    }
/**
     * 返回多重布局类型
     *
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        return isMoreType ? decodeID(decide(getItem(position)))
                : super.getItemViewType(position);
    }
    //我们需要封装一个方法,由子类来重写此方法根据传入Item数据决定返回哪一个布局 ,不定为abstract方法只因为此方法不一定要实现
     /**
     * 根据数据判断返回的布局
     *
     * @param t
     * @return layoutID
     */
    public int decide(T t) {
        return 0;
    }

而又由于getItemViewType(int position)返回的数值需要在0到getViewTypeCount这个方法返回数值之间:所以,我们需要一个方法来转化下布局ID的数值:

 /**
     * 由于官方规定,getItemViewType返回数值需在0~mDatas.size()之间,所以需要将布局ID转化下
     *
     * @param layoutid
     * @return
     */
    private int decodeID(int layoutid) {
        for (int i = 0; i < mlayoutIDs.length; i++) {
            if (layoutid == mlayoutIDs[i]) {
                return i;
            }
        }
        return 0;
    }

那么,现在的getView()方法就需要这样写了:

 @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    //由isMoreType判断是否是多重行布局
        ViewHolder holder = isMoreType ?
        //如果是我们就需要转换getItemViewType方法回传的数值转化为LayoutID
                ViewHolder.get(mContext, convertView, parent, encodeID(getItemViewType(position)), position)
                : ViewHolder.get(mContext, convertView, parent, mItemlayoutID, position);
        convert(holder, getItem(position));
        return holder.getConvertView();
    }

    private int encodeID(int index) {
        return mlayoutIDs[index];
    }

那么,现在完整的CommonAdapter代码就是这样:

package com.puti.adapter;

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

import java.util.List;

/**
 * 创建于 by puti on 2017/2/24.
 */

public abstract class CommonAdapter<T> extends BaseAdapter {
    //数据集合
    protected List<T> mDatas;
    //复杂行布局所有布局ID
    private int[] mlayoutIDs;
    //单独行布局ID
    private int mItemlayoutID;
    //上下文
    protected Context mContext;
    //是否是复杂行布局
    private boolean isMoreType = false;

    /**
     * 实现单独行布局构造方法
     *
     * @param context    上下文
     * @param datas      数据源
     * @param itemlayout 布局ID
     */
    public CommonAdapter(Context context, List<T> datas, int itemlayout) {
        this.mContext = context;
        this.mDatas = datas;
        this.mItemlayoutID = itemlayout;
    }

    /**
     * 实现复杂行布局构造方法
     *
     * @param context 上下文
     * @param datas   数据源
     * @param layouts 复杂行布局ID数组
     */
    public CommonAdapter(Context context, List<T> datas, int[] layouts) {
        this.mContext = context;
        this.mlayoutIDs = layouts;
        this.mDatas = datas;
        this.isMoreType = true;
    }

    /**
     * 返回多重布局数量
     *
     * @return
     */
    @Override
    public int getViewTypeCount() {
        return isMoreType ? mlayoutIDs.length :
                super.getViewTypeCount();
    }

    /**
     * 返回多重布局类型
     *
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        return isMoreType ? decodeID(decide(getItem(position)))
                : super.getItemViewType(position);
    }

    /**
     * 根据数据判断返回的布局
     *
     * @param t
     * @return layoutID
     */
    public int decide(T t) {
        return 0;
    }

    ;

    /**
     * 由于官方规定,getItemViewType返回数值需在0~mDatas.size()之间,所以需要将布局ID转化下
     *
     * @param layoutid
     * @return
     */
    private int decodeID(int layoutid) {
        for (int i = 0; i < mlayoutIDs.length; i++) {
            if (layoutid == mlayoutIDs[i]) {
                return i;
            }
        }
        return 0;
    }

    private int encodeID(int index) {
        return mlayoutIDs[index];
    }

    @Override
    public int getCount() {
        return mDatas == null ? 0 : mDatas.size();
    }

    @Override
    public T 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 holder = isMoreType ?
                ViewHolder.get(mContext, convertView, parent, encodeID(getItemViewType(position)), position)
                : ViewHolder.get(mContext, convertView, parent, mItemlayoutID, position);
        convert(holder, getItem(position));
        return holder.getConvertView();
    }

    /**
     * 用来设置布局
     *
     * @param holder 保存了Item的控件
     * @param t      行数据
     */
    public abstract void convert(ViewHolder holder, T t);
}

接下来,我们来实验下:

package com.puti;

import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ListView;

import com.puti.adapter.CommonAdapter;
import com.puti.adapter.ViewHolder;
import com.puti.model.Bean;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private List<Bean> data;
    private ListView mListView;
    private int[] layoutIds;
    private MyAdapter myAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
        init();
    }

    /**
     * 控件与数据绑定
     */
    private void init() {
        myAdapter = new MyAdapter(this, data, layoutIds);
        mListView.setAdapter(myAdapter);
    }

    /**
     * 初始化控件
     */
    private void initView() {
        mListView = (ListView) findViewById(R.id.listview);
    }

    /**
     * 创建数据源
     */
    private void initData() {
        layoutIds = new int[]{R.layout.layout1, R.layout.layout2, R.layout.layout3};
        data = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            switch (i % 3) {
                case 0:
                    data.add(new Bean("类型为1的标题" + i, "#ff0000", "类型1"));
                    break;
                case 1:
                    data.add(new Bean("类型为2的标题" + i, "#00ff00", "类型2"));
                    break;
                case 2:
                    data.add(new Bean("类型为3的标题" + i, "#0000ff", "类型3"));
                    break;
            }
        }
    }

    class MyAdapter extends CommonAdapter<Bean> {
        public MyAdapter(Context context, List<Bean> datas, int[] layouts) {
            super(context, datas, layouts);
        }

        @Override
        public void convert(ViewHolder holder, Bean bean) {
            switch (getItemViewType(holder.getPosition())) {
                case 0:
                    holder.setText(R.id.textView, bean.getTitle())
                            .setBackgroundColor(Color.parseColor(bean.getColor()));
                    break;
                case 1:
                    holder.setText(R.id.textView, bean.getTitle())
                            .setBackgroundColor(Color.parseColor(bean.getColor()));
                    break;
                case 2:
                    holder.setText(R.id.textView, bean.getTitle())
                            .setBackgroundColor(Color.parseColor(bean.getColor()));
                    break;

            }
        }

        //重写此方法
        @Override
        public int decide(Bean bean) {
            if (bean.getType().equals("类型1")) {
                return R.layout.layout1;
            } else if (bean.getType().equals("类型2")) {
                return R.layout.layout2;
            } else if (bean.getType().equals("类型3")) {
                return R.layout.layout3;
            }
            return 0;
        }
    }
}

简单写了一个数据Bean类:

package com.puti.model;

/**
 * 创建于 by puti on 2017/2/24.
 */

public class Bean {
    //标题
    private String title;
    //颜色
    private String color;
    //类型
    private String type;

    public Bean(String title, String color, String type) {
        this.title = title;
        this.color = color;
        this.type = type;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

再之后就是布局:

//主界面就放了一个ListView,当然GridView也是行的
<?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.puti.MainActivity">

    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

再而是三个类型的布局:
layout1:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_weight="1"
        android:gravity="start"
        android:text="TextView" />
</LinearLayout>

layout2:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="68dp"
        android:layout_weight="1"
        android:gravity="center"
        android:text="TextView" />
</LinearLayout>

layout3:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="58dp"
        android:layout_weight="1"
        android:gravity="end"
        android:text="TextView" />
</LinearLayout>

只为了简单演示效果
这是效果图:
效果图

第一次写博客,写的不好不完善之处请见谅。以后慢慢改善

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值