Android如何复用同一个RecyclerViewAdapter

前言

对于一个Android应用来说,列表是经常需要使用的一个控件,而实现列表对于Android系统来说一般就是使用ListView和RecycleView这两个,由于RecycleView更为灵活高效,所以大部分Android开发者更倾向于RecycleView。大家都知道如果要使用RecycleView必须要有一个与之相伴的Adapter。但是大部分简单的列表的Adapter功能都一致,那可不可以只使用一个BaseAdapter来控制RecyclerView呢,这样可能减少很多重复性的代码,我最近正好写了一个简单的项目。整个项目只使用了一个BaseAdapter。下面我来给读者分享一下我的尝试。

内容

其实这个东西很简单,就是一个抽象的过程。把大部分列表都需要的功能都抽象出来做成一个通用的类,然后剩下的数据加载。View控制这些多态性的东西留给使用者自己去实现就可以了,总之就是用最少的代码实现相同的功能。

那么那些东西是通用的呢,来举例子吧

  • 设置一个List数据集合
  • 重写getItemCount()
  • 重写onCreateViewHolder(单布局)
  • 追加数据到集合末尾,适用于分页加载
  • 设置数据源并更新界面,适用于下啦刷新
  • 删除某个子项数据并更新界面
  • 通过某个下标获取数据
  • 设置item的点击事件监听
  • 通过反射加载ViewHolder

上述的都是可以抽象出来的。这个时候我们就可以自己先实现一个BaseAdapter去实现这些基础的功能,然后有那么东西是不可以抽象出来的呢?
同样举例

  • onBindViewHolder()

这个接口是留给Adapter用户将数据加载到对应的View上的,每个列表的控件和数据都不一样,自然是不可以复用的。所以这个要作为一个接口留出来给使用者重写。那么明白了就来看看代码吧

public class BaseRecycleAdapter<T, V extends BaseViewHolder<T>> extends RecyclerView.Adapter<V> {

  protected List<T> dataList;

  private int layoutId;

  private OnItemClickListener<T> onItemClickListener;

  private OnItemLongClickListener<T> onItemLongClickListener;

  private Context mContext;
  private Class<V> vClass;

  public BaseRecycleAdapter(List<T> dataList, int layoutId, Class<V> vClass) {
    this.dataList = dataList;
    this.layoutId = layoutId;
    this.vClass = vClass;
  }

  public BaseRecycleAdapter(int layoutId, Class<V> vClass) {
    this(null, layoutId, vClass);
  }

  @NonNull
  @Override
  public V onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
    if (mContext == null) mContext = viewGroup.getContext();
    try {
      Constructor<V> constructor = vClass.getConstructor(View.class);
      return constructor.newInstance(newItemView(viewGroup, layoutId));
    } catch (NoSuchMethodException | IllegalAccessException
        | InstantiationException | InvocationTargetException e) {
      e.printStackTrace();
      LogHelper.log("反射失败");
    }
    return null;
  }

  private View newItemView(ViewGroup viewGroup, int resId) {
    return LayoutInflater.from(viewGroup.getContext()).inflate(resId, viewGroup, false);
  }

  @Override
  public void onBindViewHolder(@NonNull V baseViewHolder, int i) {
    baseViewHolder.setData(dataList.get(i));
    baseViewHolder.loadItemData(mContext, dataList.get(i), i);
    if (onItemClickListener != null) {
      baseViewHolder.setOnClickListener(v
          -> onItemClickListener.onItemClick(dataList.get(i), v, i));
    }
    if (onItemLongClickListener != null) {
      baseViewHolder.itemView.setOnLongClickListener(v ->
          onItemLongClickListener.onItemLongClick(dataList.get(i), i));
    }
  }

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

  public void appendDataToList(T data) {
    if (dataList == null) {
      dataList = new ArrayList<>();
    }
    dataList.add(data);
    notifyItemInserted(getItemCount() - 1);
  }

  /**
   * 用于上拉加载更多更新界面
   */
  public void appendDataToList(List<T> datas) {
    int firstPosition = getItemCount();
    dataList.addAll(datas);
    int lastPosition = getItemCount();
    for (int i = firstPosition; i < lastPosition; i++) {
      notifyItemInserted(i);
    }
  }

  public T getItemData(int position) {
    if (0 <= position && position < getItemCount()) {
      return dataList.get(position);
    }
    return null;
  }

  /**
   * 用于下啦
   */
  public void setDataList(List<T> dataList) {
    this.dataList = dataList;
    notifyDataSetChanged();
  }

  public void removeItemFormList(int position) {
    if (position < getItemCount()) {
      dataList.remove(position);
      notifyItemRemoved(position);
      for (int i = position; i < dataList.size(); i++) {
        notifyItemChanged(i);
      }
    }
  }

  public List<T> getData() {
    return dataList == null ? new ArrayList<>(0) : dataList;
  }

  public void refreshItemData(T data, int position) {
    dataList.set(position, data);
    notifyItemChanged(position);
  }

  public void refreshRangeData(int start, List<T> datas) {
    int end = start + datas.size();
    if (start < 0 || end > getItemCount()) {
      return;
    }
    for (int i = start; i < end; i++) {
      dataList.set(i, datas.get(i - start));
    }
    notifyItemRangeChanged(start, datas.size());
  }

  public void setOnItemClickListener(OnItemClickListener<T> onItemClickListener) {
    this.onItemClickListener = onItemClickListener;
  }

  public void setOnItemLongClickListener(
      OnItemLongClickListener<T> onItemLongClickListener) {
    this.onItemLongClickListener = onItemLongClickListener;
  }
}

下面是点击和长按事件监听代码:

//点击事件监听
public interface OnItemClickListener<D> {

  void onItemClick(D itemData, View view,int position);

}

//长按事件监听
public interface OnItemLongClickListener<D> {

  boolean onItemLongClick(D data, int position);

}

这是我实现的一个通用的单布局Adapter,从这里看,是不是只有一些必须或者通用方法的实现。因为onBindViewHolder这个方法里面item的布局更新是需要留给使用者重写的,所以把这个留给继承BaseViewHolder并重写loadItemData的子ViewHolder来实现
看一下BaseViewHolder的代码

public abstract class BaseViewHolder<T> extends RecyclerView.ViewHolder {

  public int currentPosition;

  private T data;

  public BaseViewHolder(@NonNull View itemView) {
    super(itemView);
    initItemView(itemView);
  }

  protected abstract void initItemView(View view);

  public abstract void loadItemData(Context context, T data, int position);

  public void onViewRecycled() {

  }

  public T getData() {
    return data;
  }

  public void setData(T data) {
    this.data = data;
  }

  public void setOnClickListener(View.OnClickListener onClickListener) {
    itemView.setOnClickListener(onClickListener);
  }
}

使用者只需要继承BaseViewHolder并重写抽象方法,在initItemView中findView在loadItemData中重写布局更新,然后在Activity直接服用BaseAdapter即可

如下所示
先实现一个ViewHolder

public class CourseDataViewHolder extends BaseViewHolder<Boolean> {

  @BindView(R.id.tv_number)
  TextView tvNumber;
  @BindView(R.id.cv_date)
  CardView cvDate;

  public CourseDataViewHolder(@NonNull View itemView) {
    super(itemView);
  }

  @Override
  protected void initItemView(View view) {
    ButterKnife.bind(this, view);
  }

  @Override
  public void loadItemData(Context context, Boolean data, int position) {
    if (data) {
      cvDate.setCardBackgroundColor(UiHelper.getColor(R.color.colorPrimary));
    } else {
      cvDate.setCardBackgroundColor(UiHelper.getColor(R.color.bg_no));
    }
    tvNumber.setText(String.valueOf(position + 1));
  }
}

然后这样复用即可

new BaseRecycleAdapter<>(data, R.layout.item_course_date, CourseDataViewHolder.class);

同理当需要使用Adapter的通用方法时,直接调用即可。比如给item添加点击监听

代码如下所示

  baseRecycleAdapter.setOnItemClickListener(new OnItemClickListener() {
      @Override
      public void onItemClick(Object itemData, View view, int position) {
        //重写item被点击后要处理的事情
      }
    });

总结

其实这只是一个简单的抽象过程。使用多了,也就明白了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容