RecyclerView.Adapter的封裝(RecyclerAdapter)

weixin_34377065發表於2018-03-27

思路

  • ListView中已經自帶了新增頭佈局和新增底部佈局的方法,但是在RecyclerView中,卻沒有預設實現,這導致在實現一些特殊佈局中不是那麼的方便.
  • 在實現RecyclerView.Adapter的時候,有很多相同重複的程式碼,比如新增或者修改資料來源、載入ViewHolder的佈局檔案
  • 本次就是通過封裝RecyclerView.Adapter,實現頭部(header)、底部(footer)、空頁面、ViewHolder.
  • 本次封裝用到了反射獲取佈局檔案和ViewHolder,泛型指定資料來源,使用dataBinding載入資料,使程式碼更加簡潔
  • 提供整個cell對外的點選事件

dataBinding的使用

  • 需要在gradle檔案裡面新增
dataBinding {
    enabled = true
}
複製程式碼
  • 不瞭解dataBinding的使用的,請百度搜尋一下

BaseViewHolder的基本封裝

  • 使用泛型+dataBinding的形式,實現資料的繫結和更新
public class BaseViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {
    public T t;

    /**
     * 是否用dataBanding
     * @param v
     * @param isBinding
     */
    public BaseViewHolder(View v,boolean isBinding){
        super(v);
        if(isBinding){
            t = DataBindingUtil.bind(v);
        }
    }
    public BaseViewHolder(View v) {
        this(v,true);
    }
}
複製程式碼

佈局檔案和ViewHolder的註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Adapter {
    /**
     * 佈局檔案
     * @return
     */
    @LayoutRes int layout();

    /**
     * ViewHolder的class
     * @return
     */
    Class<? extends BaseViewHolder> holder();
}
複製程式碼

定義頭部和底部的ViewHolder

/**
 * 頭部和底部的ViewHolder
 */
static class HeaderAndFooterViewHolder extends BaseViewHolder {
    public HeaderAndFooterViewHolder(View itemView) {
        super(itemView, false);
    }
}
複製程式碼

RecyclerAdapter的封裝

  • 通過反射+註解的方式獲取cell的佈局檔案和ViewHolder的實體
  • 定義頭部、底部、空頁面的不同型別,重寫getItemViewType方法,更具下標,是否有頭部和底部檢視,是否需要載入空頁面,來返回不同的型別.
  • 重寫onCreateViewHolder方法,根據不同的型別建立不同的ViewHolder.
  • 重寫onBindViewHolder方法,如果當前下標的型別是頭部、底部、空頁面,就不載入資料來源,對外提供cell的點選事件
  • GridLayoutManger和StaggeredGridLayoutManager跨列問題
  • 程式碼如下
public abstract class RecyclerAdapter<M, VH extends BaseViewHolder>
        extends RecyclerView.Adapter<BaseViewHolder>{

    private List<M> mData;
    /**
     * 頭部型別
     */
    public static final int TYPE_HEADER = -1;
    /**
     * 底部型別
     */
    public static final int TYPE_FOOTER = -2;
    /**
     * 無資料型別
     */
    public static final int TYPE_EMPTY = -3;
    /**
     * 正常的item
     */
    public static final int TYPE_NORMAL = 0;
    /**
     * 頭部
     */
    private View mHeaderView;

    /**
     * 底部
     */
    private View mFooterView;
    /**
     * 是否展示空頁面
     */
    private boolean showEmpty;
    /**
     * 空頁面layout
     */
    private int emptyLayout = R.layout.view_load_empty;
    /**
     * 是否是第一次載入資料
     */
    private boolean firstLoad = true;
    private RecyclerAdapter.OnItemClickListener mListener;
    /**
     * 分頁處理器
     */
    private IPageControl mPageControl;

    public RecyclerAdapter() {
        this(null);
    }

    public RecyclerAdapter(IPageControl pageControl) {
        mData = new ArrayList<>();
        mPageControl = pageControl;
        annotationAdapter();
    }

    private Adapter mAdapterAnnotation;

    /**
     * 獲取@Adapter註解
     */
    private void annotationAdapter() {
        Class cls = this.getClass();
        if (cls.isAnnotationPresent(Adapter.class)) {
            mAdapterAnnotation = this.getClass().getAnnotation(Adapter.class);
        }
    }

    /**
     * 設定空頁面
     */
    public void setEmptyLayout(@LayoutRes int emptyLayout) {
        this.emptyLayout = emptyLayout;
    }

    /**
     * 新增頭部
     */
    public void setHeaderView(View headerView) {
        mHeaderView = headerView;
    }

    /**
     * 獲取頭部
     */
    public View getHeaderView() {
        return mHeaderView;
    }

    /**
     * 新增底部
     */
    public void setFooterView(View footerView) {
        mFooterView = footerView;
    }

    /**
     * 獲取底部
     */
    public View getFooterView() {
        return mFooterView;
    }

    /**
     * 設定每行點選事件的監聽
     */
    public void setOnItemClickListener(RecyclerAdapter.OnItemClickListener listener) {
        mListener = listener;
    }

    public List<M> getData() {
        if (ListUtils.isEmpty(mData)) {
            throw new IllegalStateException("mData is empty(資料為空)");
        }
        return mData;
    }

    public M get(int position) {
        if (position < 0 || position > (mData.size() - 1)) {
            throw new IllegalStateException("position必須大於0,且不能大於mData的個數");
        }
        if (ListUtils.isEmpty(mData)) {
            return null;
        }
        return mData.get(position);
    }

    /**
     * 設定list為這個list
     */
    public void set(List<M> data) {
        firstLoad = false;
        if (data != null) {
            mData = data;
        }
        notifyDataSetChanged();
    }

    /**
     * 清空資料
     */
    public void clear() {
        mData.clear();
        notifyDataSetChanged();
    }

    /**
     * list中新增更多的資料
     */
    public void add(List<M> data) {
        if (mData == null) {
            return;
        }
        mData.addAll(data);
        notifyDataSetChanged();
    }

    

    @Override
    public int getItemViewType(int position) {
        if (position == 0 && showEmpty) {
            //當前資料空位,展示空頁面
            return TYPE_EMPTY;
        }
        if (position == 0 && mHeaderView != null) {
            //當前view是頭部資訊
            return TYPE_HEADER;
        }
        if (position == getItemCount() - 1 && mFooterView != null) {
            //當前view是底部資訊
            return TYPE_FOOTER;
        }

        return getCenterViewType(position);
    }

    /**
     * 標準的item的型別
     *
     * @return 返回引數不能小於0
     */
    @IntRange(from = 0)
    public int getCenterViewType(int position) {
        return TYPE_NORMAL;
    }

    @Override
    public int getItemCount() {
        int size = mData == null ? 0 : mData.size();
        if (mHeaderView != null) {
            //有頭部,item的個數+1
            size++;
        }
        if (mFooterView != null) {
            //有底部,item的個數+1
            size++;
        }
        if (size == 0) {
            showEmpty = true;
            size = 1;
        } else {
            showEmpty = false;
        }
        return size;
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //載入頭部資訊
        if (TYPE_HEADER == viewType) {
            return new HeaderAndFooterViewHolder(mHeaderView);
        }
        //載入底部資訊
        if (TYPE_FOOTER == viewType) {
            return new HeaderAndFooterViewHolder(mFooterView);
        }
        //載入空頁面
        if (TYPE_EMPTY == viewType) {
            View v = inflate(emptyLayout, parent);
            return new HeaderAndFooterViewHolder(v);
        }
        //反射獲取ViewHolder
        if (mAdapterAnnotation != null) {
            return reflectViewHolder(parent);
        }
        return createHolder(parent, viewType);
    }

    /**
     * 反射獲得ViewHolder
     */
    @SuppressWarnings("unchecked")
    private VH reflectViewHolder(ViewGroup parent) {
        View v = inflate(mAdapterAnnotation.layout(), parent);
        Class<VH> c = (Class<VH>) mAdapterAnnotation.holder();
        VH holder = null;
        try {
            Constructor<VH> con = c.getConstructor(View.class);
            holder = con.newInstance(v);
        } catch (NoSuchMethodException e) {
            System.err.println("檢查ViewHolder類及建構函式是否是public");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return holder;
    }

    /**
     * 除頭部和底部的ViewHolder的獲取
     *
     * @param viewType holder的型別
     */
    protected VH createHolder(ViewGroup parent, int viewType) {
        return null;
    }

    /**
     * 獲取需要viewHolder的view
     *
     * @param layoutId 佈局檔案
     */
    protected View inflate(int layoutId, ViewGroup group) {
        LayoutInflater inflater = LayoutInflater.from(group.getContext());
        return inflater.inflate(layoutId, group, false);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void onBindViewHolder(BaseViewHolder holder, final int position) {
        int index = position;
        if (mHeaderView != null) {
            //當前holder是頭部就直接返回,不需要去設定viewholder的內容
            if (getItemViewType(position) == TYPE_HEADER) {
                return;
            } else {
                /*
                 * 有頭部的情況,需要要減1,否則取item的資料會取到當前資料的下一條,
                 * 取出最後一條資料的時候,會報下標溢位
                 */
                index--;
            }
        }
        if (mFooterView != null) {
            //當前holder是底部就直接返回,不需要去設定viewholder的內容
            if (getItemViewType(position) == TYPE_FOOTER) {
                return;
            }
        }
        //空頁面狀態,不需要設定holder的內容
        if (getItemViewType(position) == TYPE_EMPTY) {
            //第一次載入資料,不展示空頁面
            if (firstLoad) {
                holder.itemView.setVisibility(View.INVISIBLE);
            } else {
                holder.itemView.setVisibility(View.VISIBLE);
            }
            return;
        }
        final int finalIndex = index;
        if (mData == null || mData.isEmpty() || index < 0 || index > mData.size() - 1) {
            return;
        }
        M m = mData.get(index);
        //設定item的點選回撥事件
        holder.itemView.setOnClickListener(v -> {
            if (mListener != null) {
                mListener.itemClick(mRecyclerView.getId(), m, finalIndex);
            }
        });

        bindViewHolder((VH) holder, m, position);
    }

    /**
     * 繫結viewHolder的資料
     */
    public abstract void bindViewHolder(VH holder, M m, int position);

    private RecyclerView mRecyclerView;

    /**
     * 處理RecyclerView.LayoutManager是GridLayoutManager型別,
     * 對頭部和底部實體進行一個處理,使其佔滿一行
     * @param recyclerView
     */
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        mRecyclerView = recyclerView;
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    return (getItemViewType(position) == TYPE_HEADER
                            || getItemViewType(position) == TYPE_FOOTER)
                            ? gridManager.getSpanCount() : 1;
                }
            });
        }
    }

    /**
     * 加入針對StaggeredGridLayoutManager跨列處理的程式碼
     * 一個item通過adapter開始顯示會被回撥
     * @param holder
     */
    @Override
    public void onViewAttachedToWindow(BaseViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        if (lp != null
                && lp instanceof StaggeredGridLayoutManager.LayoutParams
                && holder.getLayoutPosition() == 0) {
            StaggeredGridLayoutManager.LayoutParams p =
                    (StaggeredGridLayoutManager.LayoutParams) lp;
            //佔滿一行
            p.setFullSpan(true);
        }
    }

    /**
     * 頭部和底部的ViewHolder
     */
    static class HeaderAndFooterViewHolder extends BaseViewHolder {

        public HeaderAndFooterViewHolder(View itemView) {
            super(itemView, false);
        }
    }

    /**
     * item點選事件
     */
    public interface OnItemClickListener<M> {
        /**
         * @param id RecyclerView.getId()
         * @param m item下的實體
         * @param position item所在的位置
         */
        void itemClick(@IdRes int id, M m, int position);
    }
}
複製程式碼

相關文章