商城项目实战 | 9.1 Adapter 封装的全面解析

来源:互联网 发布:数据机房除尘 编辑:程序博客网 时间:2024/05/20 03:43

本文为菜鸟窝作者刘婷的连载。”商城项目实战”系列来聊聊仿”京东淘宝的购物商城”如何实现。

在之前的文章《商城项目实战 | 6.2 OkHttp 轻松封装 更加灵活的调用》中已经介绍了封装的好处和意义,同时也讲解了网络请求框架 OkHttp 的封装过程,而在这篇文章中则是要进一步讲解如何封装 Adapter,主要针对于 RecyclerView.Adapter 的封装。

封装 Adapter

之前的文章《商城项目实战 | 6.2 OkHttp 轻松封装 更加灵活的调用》已经详细介绍了封装的好处和意义,为了让代码更为的简洁化和易维护性,我们对 OkHttp 进行了封装,同样的目的,我们需要对 Adapter 进行封装。

1. 定义 BaseViewHolder

RecyclerView 的 Adapter 创建时都需要写一个单独的 RecyclerView.ViewHolder,封装 Adapter 的话,自然要先封装好相应的 ViewHolder,以便后面可以重用。创建共同的 ViewHolder,命名为 BaseViewHolder,同时继承于 RecyclerView.ViewHolder,代码如下。

public class BaseViewHolder extends RecyclerView.ViewHolder {    private SparseArray<View> views;    public BaseViewHolder(View itemView){        super(itemView);        this.views = new SparseArray<View>();    }    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 extends View> 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 中对于控件的处理都可以这样调用,非常简单,也不用多写重复的代码。

2. 定义 BaseAdapter

已经基本定义好了 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<T> datas;    public BaseAdapter(Context context, int layoutResId) {        this(context, layoutResId, null);    }    public BaseAdapter(Context context, int layoutResId, List<T> datas) {        this.datas = datas == null ? new ArrayList<T>() : 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 的子类可以扩展写入布局中控件的处理以及数据列表的装载。

3. 加入点击事件

在 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<View>();    }   @Override    public void onClick(View v) {        if (mOnItemClickListener != null) {            mOnItemClickListener.onItemClick(v, getLayoutPosition());        }    }

因为BaseViewHolder 实现了 View.OnClickListener,所以这里自然要复写 onClick 方法,在这里方法中实现 item 的点击事件。

4. 添加基本的数据操作方法

数据操作主要是增删改查,我们的商城项目中主要是列表的下拉刷新以及加载更多的处理,这里就在 BaseAdapter 中写入数据的增删操作方法。

public void clearData() {        int itemCount = datas.size();        datas.clear();        this.notifyItemRangeRemoved(0, itemCount);    }    public List<T> getDatas() {        return datas;    }    public void addData(List<T> datas) {        addData(0, datas);    }    public void addData(int position, List<T> datas) {        if (datas != null && datas.size() > 0) {            this.datas.addAll(datas);            this.notifyItemRangeChanged(position, datas.size());        }    }

clearData() 方法是用于清除数据的,addData(List datas) 方法则是添加数据,另外 getDatas() 方法可以直接获取对应 Adapter 中的数据列表,以便后面对于数据的获取。

5. 添加 SimpleAdapter

写好了 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<T> datas) {        super(context, layoutResId, datas);    }}

之后新增的 Adapter 都继承于 SimpleAdapter 就可以了,更为的方便了。

使用封装好的 Adapter

在文章《商城项目实战 | 8.2 SwipeRefreshLayout 实现可以下拉刷新和加载更多的热门商品列表》中实现了可以下拉刷新和加载更多的热门商品列表,相应列表的 Adapter 的实现写得比较繁琐,现在就通过使用封装好的 Adapter 来使代码简单起来。

1. 实现热门商品列表的 Adapter

之前在里面定义的列表 Adapter 的代码如下。

public class HotWaresAdapter  extends RecyclerView.Adapter<HotWaresAdapter.ViewHolder>  {    private List<WaresInfo> mDatas;    private LayoutInflater mInflater;    public HotWaresAdapter(List<WaresInfo> 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<WaresInfo> getDatas(){        return  mDatas;    }    public void clearData(){        mDatas.clear();        notifyItemRangeRemoved(0,mDatas.size());    }    public void addData(List<WaresInfo> datas){        addData(0,datas);    }    public void addData(int position,List<WaresInfo> 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<WaresInfo> 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。

2. 效果图

运行修改后的代码,效果图如下。

这里写图片描述

更多请关注公众号:Android技术学堂,会定期推送 Android 技术相关文章,谢谢支持。

公众号图片