本文为菜鸟窝作者刘婷的连载。”商城项目实战”系列来聊聊仿”京东淘宝的购物商城”如何实现。
在之前的文章《商城项目实战 | 6.2 OkHttp 轻松封装 更加灵活的调用》中已经介绍了封装的好处和意义,同时也讲解了网络请求框架 OkHttp 的封装过程,而在这篇文章中则是要进一步讲解如何封装 Adapter,主要针对于 RecyclerView.Adapter 的封装。
之前的文章《商城项目实战 | 6.2 OkHttp 轻松封装 更加灵活的调用》已经详细介绍了封装的好处和意义,为了让代码更为的简洁化和易维护性,我们对 OkHttp 进行了封装,同样的目的,我们需要对 Adapter 进行封装。
RecyclerView 的 Adapter 创建时都需要写一个单独的 RecyclerView.ViewHolder,封装 Adapter 的话,自然要先封装好相应的 ViewHolder,以便后面可以重用。创建共同的 ViewHolder,命名为 BaseViewHolder,同时继承于 RecyclerView.ViewHolder,代码如下。
public class BaseViewHolder extends RecyclerView.ViewHolder {
private SparseArray views;
public BaseViewHolder(View itemView){
super(itemView);
this.views = new SparseArray();
}
public TextView getTextView(int viewId) {
return retrieveView(viewId);
}
public Button getButton(int viewId) {
return retrieveView(viewId);
}
public ImageView getImageView(int viewId) {
return retrieveView(viewId);
}
public View getView(int viewId) {
return retrieveView(viewId);
}
protected T retrieveView(int viewId) {
View view = views.get(viewId);
if (view == null) {
view = itemView.findViewById(viewId);
views.put(viewId, view);
}
return (T) view;
}
}
因为在 item 的布局文件中不知道具体会有多少相应的 View,所以在 ViewHolder 中使用了 SparseArray 来装载相应的 View 集合。另外,对于控件的调用如果每次都写一次也太过于繁琐了,所以这里也直接使用 retrieveView(int viewId) 方法传入 View 的 id 值,并且也可以根据所要返回的不同控件来写相应的方法,例如 getImageView() 直接返回 ImageView,而 getTextView() 直接返回 TextView。
TextView textView = viewHolder.getTextView(R.id.text);
上面是 getTextView() 方法的调用,返回 TextView, 后期在 Adapter 中对于控件的处理都可以这样调用,非常简单,也不用多写重复的代码。
已经基本定义好了 BaseViewHolder 了,下面就在新创建的 BaseAdapter 中使用它。新建 BaseAdapter,然后继承于 RecyclerView.Adapter ,同时数据类型传入泛型,这样不同数据类型的 List 也可以直接传入到 BaseAdapter 中了。
public abstract class BaseAdapter<T, H extends BaseViewHolder> extends RecyclerView.Adapter<BaseViewHolder> {
protected static final String TAG = BaseAdapter.class.getSimpleName();
protected final Context context;
protected final int layoutResId;
protected List datas;
public BaseAdapter(Context context, int layoutResId) {
this(context, layoutResId, null);
}
public BaseAdapter(Context context, int layoutResId, List datas) {
this.datas = datas == null ? new ArrayList() : datas;
this.context = context;
this.layoutResId = layoutResId;
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(layoutResId, viewGroup, false);
BaseViewHolder vh = new BaseViewHolder(view, mOnItemClickListener);
return vh;
}
@Override
public void onBindViewHolder(BaseViewHolder viewHoder, int position) {
T item = getItem(position);
convert((H) viewHoder, item);
}
@Override
public int getItemCount() {
if (datas == null || datas.size() <= 0)
return 0;
return datas.size();
}
public T getItem(int position) {
if (position >= datas.size())
return null;
return datas.get(position);
}
/**
* Implement this method and use the helper to adapt the view to the given item.
*
* @param viewHoder A fully initialized helper.
* @param item The item that needs to be displayed.
*/
protected abstract void convert(H viewHoder, T item);
}
在 BaseAdapter 中写好 Adapter 的一些基本方法,同时这里的抽象方法 convert (H viewHoder, T item) 主要是用于继承于 BaseAdapter 的子类可以扩展写入布局中控件的处理以及数据列表的装载。
在 RecyclerView 的 Adapter 中和 ListView 的 Adapter 有个很大的不同之处就是 ListView 本身提供了一个 setOnItemClickListener(AdapterView.OnItemClickListener listener) 方法用于对选项的点击事件的监听,但是 RecyclerView 的 item 项的点击事件需要自己定义,所以在封装的 BaseAdapter 中肯定要加入了。
private OnItemClickListener mOnItemClickListener = null;
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.mOnItemClickListener = listener;
}
而这个事件监听最终是要作用于选项的点击,所以在 BaseViewHolder 也需要添加点击事件的监听,首先声明监听事件。
private BaseAdapter.OnItemClickListener mOnItemClickListener;
BaseViewHolder 中 implements View.OnClickListener ,同时之前 BaseViewHolder 的构造方法修改如下。
public BaseViewHolder(View itemView, BaseAdapter.OnItemClickListener onItemClickListener) {
super(itemView);
itemView.setOnClickListener(this);
this.mOnItemClickListener = onItemClickListener;
this.views = new SparseArray();
}
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(v, getLayoutPosition());
}
}
因为BaseViewHolder 实现了 View.OnClickListener,所以这里自然要复写 onClick 方法,在这里方法中实现 item 的点击事件。
数据操作主要是增删改查,我们的商城项目中主要是列表的下拉刷新以及加载更多的处理,这里就在 BaseAdapter 中写入数据的增删操作方法。
public void clearData() {
int itemCount = datas.size();
datas.clear();
this.notifyItemRangeRemoved(0, itemCount);
}
public List getDatas() {
return datas;
}
public void addData(List datas) {
addData(0, datas);
}
public void addData(int position, List datas) {
if (datas != null && datas.size() > 0) {
this.datas.addAll(datas);
this.notifyItemRangeChanged(position, datas.size());
}
}
clearData() 方法是用于清除数据的,addData(List datas) 方法则是添加数据,另外 getDatas() 方法可以直接获取对应 Adapter 中的数据列表,以便后面对于数据的获取。
写好了 BaseAdapter 还是不够完善,还希望 Adapter 的实现和调用更加简单些,就要在添加一个 SimpleAdapter,用于继承 BaseAdapter。
public abstract class SimpleAdapter<T> extends BaseAdapter<T, BaseViewHolder> {
public SimpleAdapter(Context context, int layoutResId) {
super(context, layoutResId);
}
public SimpleAdapter(Context context, int layoutResId, List datas) {
super(context, layoutResId, datas);
}
}
之后新增的 Adapter 都继承于 SimpleAdapter 就可以了,更为的方便了。
在文章《商城项目实战 | 8.2 SwipeRefreshLayout 实现可以下拉刷新和加载更多的热门商品列表》中实现了可以下拉刷新和加载更多的热门商品列表,相应列表的 Adapter 的实现写得比较繁琐,现在就通过使用封装好的 Adapter 来使代码简单起来。
之前在里面定义的列表 Adapter 的代码如下。
public class HotWaresAdapter extends RecyclerView.Adapter {
private List mDatas;
private LayoutInflater mInflater;
public HotWaresAdapter(List wares){
mDatas = wares;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
mInflater = LayoutInflater.from(parent.getContext());
View view = mInflater.inflate(R.layout.recycler_item_wares_layout,null);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
WaresInfo wares = getData(position);
holder.draweeView.setImageURI(Uri.parse(wares.getImgUrl()));
holder.textTitle.setText(wares.getName());
holder.textPrice.setText("¥"+wares.getPrice());
}
public WaresInfo getData(int position){
return mDatas.get(position);
}
public List getDatas(){
return mDatas;
}
public void clearData(){
mDatas.clear();
notifyItemRangeRemoved(0,mDatas.size());
}
public void addData(List datas){
addData(0,datas);
}
public void addData(int position,List datas){
if(datas !=null && datas.size()>0) {
mDatas.addAll(datas);
notifyItemRangeChanged(position, mDatas.size());
}
}
@Override
public int getItemCount() {
if(mDatas!=null && mDatas.size()>0)
return mDatas.size();
return 0;
}
class ViewHolder extends RecyclerView.ViewHolder{
SimpleDraweeView draweeView;
TextView textTitle;
TextView textPrice;
public ViewHolder(View itemView) {
super(itemView);
draweeView = (SimpleDraweeView) itemView.findViewById(R.id.drawee_view);
textTitle= (TextView) itemView.findViewById(R.id.text_title);
textPrice= (TextView) itemView.findViewById(R.id.text_price);
}
}
}
这也是我们一般定义列表 Adapter 所要写的。现在我们封装了 Adapter 就直接使用起来看下,同样也是现实可以下拉刷新和加载更多的热门商品列表 Adapter,但是我们所要实现的代码却大大的简洁了,新建 HWAdapter,具体实现如下。
public class HWAdapter extends SimpleAdapter<WaresInfo> {
public HWAdapter(Context context, List datas) {
super(context, R.layout.recycler_item_wares_layout, datas);
}
@Override
protected void convert(BaseViewHolder viewHolder, WaresInfo wares) {
SimpleDraweeView draweeView = (SimpleDraweeView) viewHolder.getView(R.id.drawee_view);
draweeView.setImageURI(Uri.parse(wares.getImgUrl()));
viewHolder.getTextView(R.id.text_title).setText(wares.getName());
}
}
对比新建的 HWAdapter 和之前的 HotWaresAdapter,封装之后果然是不一样的。而在热门模块 HotFragment 中对于 HWAdapter 的调用和之前都是一样的,将 HotWaresAdapter 替换为 HWAdapter 就可以了,在 showData() 方法中修改,这里还是贴一下代码,修改之后的 showData() 方法如下。
private void showData() {
switch (state) {
case STATE_NORMAL:
mAdatper = new HWAdapter(getActivity(), datas);
recyclerView.setAdapter(mAdatper);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.addItemDecoration(new WareItemDecoration(getContext(), WareItemDecoration.VERTICAL_LIST));
break;
case STATE_REFREH:
mAdatper.clearData();
mAdatper.addData(datas);
recyclerView.scrollToPosition(0);
layoutRefresh.finishRefresh();
break;
case STATE_MORE:
mAdatper.addData(mAdatper.getDatas().size(), datas);
recyclerView.scrollToPosition(mAdatper.getDatas().size());
layoutRefresh.finishRefreshLoadMore();
break;
}
}
state 的三种模式分为正常状态、刷新状态以及加载更多的状态,处理和之前基本一样,只是替换为了新建的 HWAdapter。
运行修改后的代码,效果图如下。
更多请关注公众号:Android技术学堂,会定期推送 Android 技术相关文章,谢谢支持。