【从 0 开始开发一款直播 APP】3.1 高层封装之 Adapter — ListView & GridView

本文为菜鸟窝作者蒋志碧的连载。“从 0 开始开发一款直播 APP ”系列来聊聊时下最火的直播 APP,如何完整的实现一个类"腾讯直播"的商业化项目
视频地址:http://www.cniao5.com/course/10121


【从 0 开始开发一款直播 APP】3.1 高层封装之 Adapter — ListView & GridView
【从 0 开始开发一款直播 APP】3.2 高层封装之 Adapter — RecyclerView 实现单布局展示
【从 0 开始开发一款直播 APP】3.3 高层封装之 Adapter -- RecyclerView 实现多条目展示
【从 0 开始开发一款直播 APP】3.4 高层封装之 Adapter -- RecyclerView 优雅的添加 Header、Footer


一、前言

我们在开发中写得最多的就是对 ListView、GridView 的适配器,我们熟悉得不能再熟悉,Adapter 一般都是继承 BaseAdapter 并复写其中的方法,getView 里面使用 ViewHolder 绑定控件。

二、常见示例

public class TraditionAdapter extends BaseAdapter {

    private Context mContext;
    private List<Item> mItems;

    public TraditionAdapter(Context context, List<Item> items) {
        mContext = context;
        mItems = items;
    }

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

    @Override
    public Object getItem(int position) {
        return mItems.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
      
        if (holder == null){
            convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item,parent,false);
            holder = new ViewHolder();
            
            holder.titleText = (TextView) convertView.findViewById(R.id.tv1);
            holder.descText = (TextView) convertView.findViewById(R.id.tv2);
            holder.img = (ImageView) convertView.findViewById(R.id.img);
          
            convertView.setTag(holder);
        }else {
            holder = (ViewHolder) convertView.getTag();
        }
      
        Item item = mItems.get(position);
        holder.titleText.setText(item.getTv1());
        holder.descText.setText(item.getTv2());
        holder.img.setImageResource(item.getRes());
        return convertView;
    }

    class ViewHolder{
        TextView titleText;
        TextView descText;
        ImageView img;
    }
}

这种重复的代码大家应该都写了很多遍了,TraditionAdapter 继承 BaseAdapter,getView 使用 ViewHolder 绑定控件。而通常每个 ListView 布局都会有一个 Adapter,Adapter 中也会有一个对应的 ViewHolder。由此看来,要减少代码量就要将 Adapter、ViewHolder 封装成通用类。

想要 AdapterViewHolder 通用,目前需要以下几步:

2.1、数据是活的,TraditionAdapter 中的 Item 类应作为范型传入 Adapter

2.2、getCount()、getItem()、getItemId() 这三个方法一直都不变,将其封装

2.3、抽取 ViewHolder 类,解决控件绑定问题

2.4、Adapter 类封装,实现与 ViewHolder 神匹配

知道了大概步骤,先来封装 ViewHolder 吧。

三、ViewHodler 类封装

ViewHolder 通过 convertView.setTag() 与 convertView 进行绑定,然后当 convertView复用时,直接利用 convertView.getTag() 获取的ViewHolder 的 convertView 布局中的控件,省去了findViewById() 的时间

实际上每个 convertView 会绑定一个 ViewHolder 对象,这个 ViewHolder 主要用于帮 convertView 存储布局中的控件。

那么我们只要写出一个通用的 ViewHolder,对于任意的 convertView,提供一个对象让其 setTag() 即可

ViewHodler 类封装,需要做以下几步:

1、返回 ViewHolder

2、获取控件

3、设置控件

4、convertView 的复用

每个布局有不同的控件,每个控件有自己的 id 和数据。存储这些控件需要使用 SparseArray。

SparseArray 简介

SparseArray 是 android 提供的新的存储键值对的 API,相比 HashMap,SparseArray 性能更好。原因如下:

1、SparseArray 更加优化内存

2、key 的类型是 int 型,免去装箱操作,时间性能优于 HashMap

3、SparseArray 结构简单,使用一位数组存储 key 和 value

开始 ViewHolder 的封装。

3.1、创建 BaseViewHolder 类,需要的变量大概有 View mConvertView 复用机制,SparseArray<View> mViews 存储所有控件,int mPosition 记录 View 位置信息

public class BaseViewHolder{
    //复用的View
    private final View mConvertView;
    //所有控件集合
    private SparseArray<View> mViews;
    //记录位置信息
    private int mPosition;

    /**
     * BaseViewHolder 构造函数
     * @param context 上下文对象
     * @param parent 父类容器
     * @param layoutId 布局 Id
     * @param position item位置信息
     */
    public BaseViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
        this.mPosition = position;
        this.mViews = new SparseArray<View>();
        mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
        //设置 tag
        mConvertView.setTag(this);
    }
  
   /**
     * 通过 viewId 获取控件
     * @param viewId 控件id
     * @param <T> View 子类
     * @return 返回 View
     */
    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;
    }

    //返回 ViewHolder
    public static BaseViewHolder getViewHolder(Context context, View convertView, ViewGroup parent,int layoutId, int position) {
        
        //BaseViewHolder 为空,创建新的,否则返回已存在的
        if (convertView == null) {
            return new BaseViewHolder(context, parent, layoutId, position);
        } else {
            BaseViewHolder holder = (BaseViewHolder) convertView.getTag();
            //更新 item 位置信息
            holder.mPosition = position;
            return holder;
        }
    }
    
   //获取 convertView
    public View getConvertView() {
        return mConvertView;
    }
}

3.2、设置控件以及监听(采用链式编程方法)

/**
 * 设置 TextView 的值
 * @param viewId
 * @param text
 * @return
 */
public BaseViewHolder setText(int viewId, String text)
{
    TextView tv = getView(viewId);
    tv.setText(text);
    return this;
}

/**
 * 设置TImageView的值
 * @param viewId
 * @param resId
 * @return
 */
public BaseViewHolder setImageResource(int viewId, int resId)
{
    ImageView view = getView(viewId);
    view.setImageResource(resId);
    return this;
}

/**
 * 设置是否可见
 * @param viewId
 * @param visible
 * @return
 */
public BaseViewHolder setVisible(int viewId, boolean visible)
{
    View view = getView(viewId);
    view.setVisibility(visible ? View.VISIBLE : View.GONE);
    return this;
}

/**
 * 设置tag
 * @param viewId
 * @param tag
 * @return
 */
public BaseViewHolder setTag(int viewId, Object tag)
{
    View view = getView(viewId);
    view.setTag(tag);
    return this;
}

public BaseViewHolder setTag(int viewId, int key, Object tag)
{
    View view = getView(viewId);
    view.setTag(key, tag);
    return this;
}
/**
 * 设置 Checkable
 * @param viewId
 * @param checked
 * @return
 */
public BaseViewHolder setChecked(int viewId, boolean checked)
{
    Checkable view = (Checkable) getView(viewId);
    view.setChecked(checked);
    return this;
}

/**
 * 点击事件
 */
public BaseViewHolder setOnClickListener(int viewId,View.OnClickListener listener)
{
    View view = getView(viewId);
    view.setOnClickListener(listener);
    return this;
}

/**
 * 触摸事件
 */
public BaseViewHolder setOnTouchListener(int viewId,View.OnTouchListener listener)
{
    View view = getView(viewId);
    view.setOnTouchListener(listener);
    return this;
}

/**
 * 长按事件
 */
public BaseViewHolder setOnLongClickListener(int viewId,View.OnLongClickListener listener)
{
    View view = getView(viewId);
    view.setOnLongClickListener(listener);
    return this;
}z

四、Adapter 类封装

Adapter 封装需要以下几步:

1、上例一直存在的 Item 类将作为范型传入Adapter

2、封装 getCount()、getItem()、getItemId() 三个方法

3、封装 getView()

4、绑定 ViewHolder

创建 BaseAdapter 继承 android.widget.BaseAdapter 类,重写方法及构造函数,根据需求,需要的参数有 List<T> mDatas 数据源,Context mContext 上下文对象,int mLayoutId 布局。

public abstract class BaseAdapter<T> extends android.widget.BaseAdapter {
    protected List<T> mDatas;
    protected Context mContext;
    protected int mLayoutId;

    public BaseAdapter(List<T> datas, Context context, int layoutId) {
        mDatas = datas;
        mContext = context;
        this.mLayoutId = layoutId;
    }

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

    @Override
    public T getItem(int position) {
        return mDatas == null ? null : mDatas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        BaseViewHolder holder = BaseViewHolder.getViewHolder(mContext, convertView, parent, mLayoutId, position);

        T t = mDatas.get(position);
        
        //抽象出 ViewHolder 让用户去实现填充数据
        bindData(holder, t);

        return holder.getConvertView();
    }

    public abstract void bindData(BaseViewHolder holder, T t);
}

五、Adapter 和 ViewHolder 的使用

**5.1、 创建 SimpleAdapter.java 继承 BaseAdapter 类,传入 Item 数据源,根据布局找到需要的控件并填充数据,添加监听等。 **

public class SimpleAdapter extends BaseAdapter<Item> {

    public SimpleAdapter(List datas, Context context) {
        super(datas, context, R.layout.list_item);
    }

    @Override
    public void bindData(BaseViewHolder holder, final Item item) {

        holder.setText(R.id.tv1,item.getTv1())
                .setText(R.id.tv2,item.getTv2())
                .setImageResource(R.id.img,item.getRes())
                .setOnClickListener(R.id.tv2, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                       Toast.makeText(mContext,item.getTv2(),Toast.LENGTH_SHORT).show();
                    }
                });
    }
}

5.2、由于加了点击事件,运行起来点击事件无效果,并不是因为代码有问题,而是焦点抢占原因,因此需要在布局文件 activity_adapter.xml 中设置是否可点击

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

    <ImageView
        android:padding="5dp"
        android:layout_marginRight="40dp"
        android:id="@+id/img"
        android:layout_width="100dp"
        android:layout_height="100dp"/>

    <LinearLayout
        android:clickable="false"
        android:padding="10dp"
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <TextView
            android:paddingTop="10dp"
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="40dp"/>

        <TextView
            android:id="@+id/tv2"
            android:clickable="true"
            android:layout_width="wrap_content"
            android:layout_height="40dp"/>
    </LinearLayout>
</LinearLayout>

5.3、 AdapterActivity.java

public class AdapterActivity extends BaseActivity {

    private SimpleAdapter mAdapter;
    private ListView mListView;
    private ArrayList<Item> Datas;

    @Override
    protected void setToolbar() {
    }

    @Override
    protected void setListener() {
    }

    //添加数据
    @Override
    protected void initData() {
        Datas = new ArrayList<>();
        for (int i = 1; i <= 30; i++) {
            Item item = new Item(R.drawable.tab_publish_normal, " get 新技能" + i, "拣到漂亮妹子 "+i+" 枚,在大街上");
            Datas.add(item);
        }

        mAdapter = new SimpleAdapter(Datas,this);
        mListView.setAdapter(mAdapter);
    }

    @Override
    protected void initView() {
          mListView = obtainView(R.id.list);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_adapter;
    }
}

六、运行效果展示

6.1、ListView

6.2、GridView

七、总结

Adapter 传统写法熟练,对 ConverView 复用了解

ViewHolder 的使用熟悉,尤其是控件绑定

了解 SparseArray 的优缺点,对其基本使用熟悉

笔者只是传达了封装的思想,能力有限,封装不到位的地方还望大家留言指正

更多内容,请关注菜鸟窝(微信公众号ID: cniao5),程序猿的在线学习平台。转载请注明出处,本文出自菜鸟窝,原文链接http://www.cniao5.com/forum/thread/2ac69d820f0611e790dc00163e0230fa

推荐阅读更多精彩内容