RecyclerView打造通用的万能Adapter
既然想做到通用那么现在摆在面前的就三个问题:数据怎么办?布局怎么办? 绑定怎么办?。数据决定采用泛型,布局打算直接构造传递,绑定显示效果肯定就只能回传。
1 基本改造
数据决定采用泛型,布局打算直接构造传递,绑定显示效果回传。
1 public abstract class CommonRecyclerAdapter<T> extends RecyclerView.Adapter<ViewHolder> { 2 3 protected Context mContext; 4 protected LayoutInflater mInflater; 5 //数据怎么办?利用泛型 6 protected List<T> mDatas; 7 // 布局怎么办?直接从构造里面传递 8 private int mLayoutId; 9 10 public CommonRecyclerAdapter(Context context, List<T> datas, int layoutId) { 11 this.mContext = context; 12 this.mInflater = LayoutInflater.from(mContext); 13 this.mDatas = datas; 14 this.mLayoutId = layoutId; 15 } 16 17 @Override 18 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 19 // 先inflate数据 20 View itemView = mInflater.inflate(mLayoutId, parent, false); 21 // 返回ViewHolder 22 ViewHolder holder = new ViewHolder(itemView); 23 return holder; 24 } 25 26 @Override 27 public void onBindViewHolder(ViewHolder holder, int position) { 28 // 绑定怎么办?回传出去 29 convert(holder, mDatas.get(position)); 30 } 31 32 /** 33 * 利用抽象方法回传出去,每个不一样的Adapter去设置 34 * @param item 当前的数据 35 */ 36 public abstract void convert(ViewHolder holder, T item); 37 38 @Override 39 public int getItemCount() { 40 return mDatas.size(); 41 } 42 }
2 实战使用
1 public class CategoryListAdapter extends CommonRecyclerAdapter<ChannelListResult. 2 DataBean.CategoriesBean.CategoryListBean>{ 3 4 public CategoryListAdapter(Context context, List<ChannelListResult.DataBean. 5 CategoriesBean.CategoryListBean> datas) { 6 super(context, datas, R.layout.channel_list_item); 7 } 8 9 @Override 10 public void convert(ViewHolder holder, ChannelListResult.DataBean.CategoriesBean.CategoryListBean item) { 11 // 从ViewHolder中去findViewById 12 TextView nameTv = (TextView) holder.itemView.findViewById(R.id.channel_text); 13 TextView channelTopicTv = (TextView) holder.itemView.findViewById(R.id.channel_topic); 14 TextView channelUpdateInfo = (TextView) holder.itemView.findViewById(R.id.channel_update_info); 15 View recommendLabel = holder.itemView.findViewById(R.id.recommend_label); 16 ImageView channelIconIv = (ImageView) holder.itemView.findViewById(R.id.channel_icon); 17 18 // 显示数据 19 nameTv.setText(item.getName()); 20 channelTopicTv.setText(item.getIntro()); 21 String str = item.getSubscribe_count() + " 订阅 | " + 22 "总帖数 <font color='#FF678D'>" + item.getTotal_updates() + "</font>"; 23 channelUpdateInfo.setText(Html.fromHtml(str)); 24 // 是否是最新 25 if (item.isIs_recommend()) { 26 recommendLabel.setVisibility(View.VISIBLE); 27 } else { 28 recommendLabel.setVisibility(View.GONE); 29 } 30 // 加载图片 31 Glide.with(mContext).load(item.getIcon_url()).centerCrop().into(channelIconIv); 32 } 33 }
代码少了不知道多少了,反正现在我们只需要关注convert()方法,但是还可以更加简单一点,我们对ViewHolder做一下优化。
3 改造ViewHolder
public class ViewHolder extends RecyclerView.ViewHolder { // 用来存放子View减少findViewById的次数 private SparseArray<View> mViews; public ViewHolder(View itemView) { super(itemView); mViews = new SparseArray<>(); } /** * 设置TextView文本 */ public ViewHolder setText(int viewId, CharSequence text) { TextView tv = getView(viewId); tv.setText(text); return this; } /** * 通过id获取view */ public <T extends View> T getView(int viewId) { // 先从缓存中找 View view = mViews.get(viewId); if (view == null) { // 直接从ItemView中找 view = itemView.findViewById(viewId); mViews.put(viewId, view); } return (T) view; } /** * 设置View的Visibility */ public ViewHolder setViewVisibility(int viewId, int visibility) { getView(viewId).setVisibility(visibility); return this; } /** * 设置ImageView的资源 */ public ViewHolder setImageResource(int viewId, int resourceId) { ImageView imageView = getView(viewId); imageView.setImageResource(resourceId); return this; } /** * 设置条目点击事件 */ public void setOnIntemClickListener(View.OnClickListener listener) { itemView.setOnClickListener(listener); } /** * 设置条目长按事件 */ public void setOnIntemLongClickListener(View.OnLongClickListener listener) { itemView.setOnLongClickListener(listener); } /** * 设置图片通过路径,这里稍微处理得复杂一些,因为考虑加载图片的第三方可能不太一样 * 也可以直接写死 */ public ViewHolder setImageByUrl(int viewId, HolderImageLoader imageLoader) { ImageView imageView = getView(viewId); if (imageLoader == null) { throw new NullPointerException("imageLoader is null!"); } imageLoader.displayImage(imageView.getContext(), imageView, imageLoader.getImagePath()); return this; } /** * 图片加载,这里稍微处理得复杂一些,因为考虑加载图片的第三方可能不太一样 * 也可以不写这个类 */ public static abstract class HolderImageLoader { private String mImagePath; public HolderImageLoader(String imagePath) { this.mImagePath = imagePath; } public String getImagePath() { return mImagePath; } public abstract void displayImage(Context context, ImageView imageView, String imagePath); } }
再次实战
@Override public void convert(ViewHolder holder, ChannelListResult.DataBean.CategoriesBean.CategoryListBean item) { // 显示数据 String str = item.getSubscribe_count() + " 订阅 | " + "总帖数 <font color='#FF678D'>" + item.getTotal_updates() + "</font>"; holder.setText(R.id.channel_text, item.getName()) .setText(R.id.channel_topic, item.getIntro()) .setText(R.id.channel_update_info, Html.fromHtml(str)); // 是否是最新 if (item.isIs_recommend()) { holder.setViewVisibility(R.id.recommend_label, View.VISIBLE); } else { holder.setViewVisibility(R.id.recommend_label, View.GONE); } // 加载图片 holder.setImageByUrl(R.id.channel_icon, new GlideImageLoader(item.getIcon_url())); }
GlideImageLoader
public class GlideImageLoader extends ViewHolder.HolderImageLoader { public GlideImageLoader(String imagePath) { super(imagePath); } @Override public void displayImage(Context context, ImageView imageView, String imagePath) { Glide.with(context).load(imagePath).placeholder(R.drawable.ic_discovery_default_channel).centerCrop().into(imageView); } }
3 多布局实现
最后我们考虑一下多布局问题,有的时候我们列表的样式各不相同,随便打个比方,比如聊天的列表样式等等。
先介绍RecyclerView.Adapter的getItemViewType(int position)这个方法,可以根据当前位置获取一个viewType最终会传到onCreateViewHolder()这个方法中,就是它了。
多布局支持接口
public interface MultiTypeSupport<T> { // 根据当前位置或者条目数据返回布局 public int getLayoutId(T item, int position); }
最终的Adapter
1 public abstract class CommonRecyclerAdapter<T> extends RecyclerView.Adapter<ViewHolder> { 2 3 protected Context mContext; 4 protected LayoutInflater mInflater; 5 //数据怎么办? 6 protected List<T> mData; 7 // 布局怎么办? 8 private int mLayoutId; 9 10 // 多布局支持 11 private MultiTypeSupport mMultiTypeSupport; 12 13 public CommonRecyclerAdapter(Context context, List<T> data, int layoutId) { 14 this.mContext = context; 15 this.mInflater = LayoutInflater.from(mContext); 16 this.mData = data; 17 this.mLayoutId = layoutId; 18 } 19 20 /** 21 * 多布局支持 22 */ 23 public CommonRecyclerAdapter(Context context, List<T> data, MultiTypeSupport<T> multiTypeSupport) { 24 this(context, data, -1); 25 this.mMultiTypeSupport = multiTypeSupport; 26 } 27 28 /** 29 * 根据当前位置获取不同的viewType 30 */ 31 @Override 32 public int getItemViewType(int position) { 33 // 多布局支持 34 if (mMultiTypeSupport != null) { 35 return mMultiTypeSupport.getLayoutId(mData.get(position), position); 36 } 37 return super.getItemViewType(position); 38 } 39 40 @Override 41 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 42 // 多布局支持 43 if (mMultiTypeSupport != null) { 44 mLayoutId = viewType; 45 } 46 // 先inflate数据 47 View itemView = mInflater.inflate(mLayoutId, parent, false); 48 // 返回ViewHolder 49 ViewHolder holder = new ViewHolder(itemView); 50 51 52 return holder; 53 } 54 55 @Override 56 public void onBindViewHolder(ViewHolder holder, final int position) { 57 // 设置点击和长按事件 58 if (mItemClickListener != null) { 59 holder.itemView.setOnClickListener(new View.OnClickListener() { 60 @Override 61 public void onClick(View v) { 62 mItemClickListener.onItemClick(position); 63 } 64 }); 65 } 66 if (mLongClickListener != null) { 67 holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { 68 @Override 69 public boolean onLongClick(View v) { 70 return mLongClickListener.onLongClick(position); 71 } 72 }); 73 } 74 75 // 绑定怎么办?回传出去 76 convert(holder, mData.get(position)); 77 } 78 79 /** 80 * 利用抽象方法回传出去,每个不一样的Adapter去设置 81 * 82 * @param item 当前的数据 83 */ 84 public abstract void convert(ViewHolder holder, T item); 85 86 @Override 87 public int getItemCount() { 88 return mData.size(); 89 } 90 91 /*************** 92 * 设置条目点击和长按事件 93 *********************/ 94 public OnItemClickListener mItemClickListener; 95 public OnLongClickListener mLongClickListener; 96 97 public void setOnItemClickListener(OnItemClickListener itemClickListener) { 98 this.mItemClickListener = itemClickListener; 99 } 100 101 public void setOnLongClickListener(OnLongClickListener longClickListener) { 102 this.mLongClickListener = longClickListener; 103 } 104 }