本文为菜鸟窝作者蒋志碧的连载。“从 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 封装成通用类。
想要 Adapter
与 ViewHolder
通用,目前需要以下几步:
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