1. 程式人生 > >RecyclerView.Adapter介面卡通用化改造

RecyclerView.Adapter介面卡通用化改造

RecyclerView 是谷歌給開發者的福利,比以往的ListView更加強大,效能更大,具體原始碼分析,我們下次講。本篇主要介紹如何在專案中提煉程式碼,增強它的適配性。下面就介紹我的思路。

首先一般編碼有如下幾個問題。
1.列表資料,需要寫一個RecyclerView.Adapter和一個ViewHolder。ViewHolder是根據不同佈局而來,但RecyclerView.Adapter則包含大量的重複程式碼是否可以只對修改程式設計?

2.專案中常見的是列表巢狀列表,資料的傳遞,在子列表介面卡,或者孫子,重孫介面卡中編碼的複雜度是否有好的解決方案?

下面貼出我的方案程式碼

public class MyCommonAdapter<T> extends RecyclerView.Adapter implements FindFather<MyCommonAdapter.ApaterParent> {

    ApaterParent apaterParent = null;

    @Override
    public ApaterParent getFather() {
        return apaterParent;
    }

    @Override
    public void admitFather(
ApaterParent father) { this.apaterParent = father; } public static abstract class ApaterParent<T> { public ApaterParent father; public abstract int getPosition(); public abstract T getBean(); public ApaterParent getFather() { return
father; } public void setFather(ApaterParent father) { this.father = father; } } public static final String TAG = MyCommonAdapter.class.getName(); /** * 列表資料集合. */ protected List<T> dataList = new ArrayList<>(); /** * 佈局填充. */ protected LayoutInflater mInflater; /** * 一個adapter 適配一個viewholder. */ private Class<T> viewHolder; /** * M context. */ protected Context mContext; private Fragment fragment; public MyCommonAdapter(Context mContext, Class<T> viewHolder) { this.viewHolder = viewHolder; this.mContext = mContext; mInflater = LayoutInflater.from(mContext); } public MyCommonAdapter(Fragment fragment, Class<T> viewHolder) { this(fragment.getActivity(), viewHolder); this.fragment = fragment; } @NonNull @Override public MyCommonAdapter.DefineHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { UseLayout layout = viewHolder.getAnnotation(UseLayout.class); if (layout == null) { throw new RuntimeException("請設定佈局註解"); } int layoutRes = 0; if (layout != null) { layoutRes = layout.layoutRes(); } try { Constructor<T> constructor = viewHolder.getConstructor(Context.class, View.class); MyCommonAdapter.DefineHolder viewHolder = (MyCommonAdapter.DefineHolder) constructor.newInstance(mContext, mInflater.inflate(layoutRes, null, false)); viewHolder.admitFather(apaterParent); viewHolder.setAdapter(this); if (fragment != null) { viewHolder.setFragment(fragment); } return viewHolder; } catch (Exception e) { LogUtils.d(TAG, "onCreateViewHolder 發生錯誤:", e.toString()); } return null; } @Override public void onBindViewHolder(final @NonNull RecyclerView.ViewHolder holder, final int position) { if (holder instanceof MyCommonAdapter.DefineHolder) { ((MyCommonAdapter.DefineHolder) holder).setCount(dataList.size()); final T itemBean = dataList.get(position); if (itemBean != null) { MyCommonAdapter.ApaterParent<T> myself = new MyCommonAdapter.ApaterParent<T>() { @Override public int getPosition() { return position; } @Override public T getBean() { return itemBean; } }; myself.setFather(getFather()); ((MyCommonAdapter.DefineHolder) holder).setData(itemBean, position, myself); } } } public static abstract class DefineHolder<T> extends RecyclerView.ViewHolder implements FindFather<ApaterParent> { private RecyclerView.Adapter adapter; ApaterParent parent = null; private Fragment fragment; private Context context; private int count; public DefineHolder(Context context, View itemView) { super(itemView); this.context = context; ButterKnife.bind(this, itemView); } public RecyclerView.Adapter getAdapter() { return adapter; } public void setAdapter(RecyclerView.Adapter adapter) { this.adapter = adapter; } public void notifyDataSetChanged() { if (adapter != null) { adapter.notifyDataSetChanged(); } } @Override public void admitFather(ApaterParent father) { parent = father; } @Override public ApaterParent getFather() { return parent; } public boolean isLastPosition() { return count - 1 == getLayoutPosition(); } public boolean isFirstPosition() { return 0 == getLayoutPosition(); } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public Fragment getFragment() { return fragment; } public void setFragment(Fragment fragment) { this.fragment = fragment; } public Context getContext() { return context; } public void setContext(Context context) { this.context = context; } public abstract void setData(final T bean, final int position, final ApaterParent myself); } public boolean isEmpty() { return dataList.isEmpty(); } @Override public int getItemCount() { return dataList.size(); } public void updateData(List<T> data, boolean isClearOld) { if (isClearOld) { dataList.clear(); } if (data != null && !data.isEmpty()) { dataList.addAll(data); } notifyDataSetChanged(); } }

通用介面卡類繼承RecyclerView.Adapter 實現FindFather<MyCommonAdapter.ApaterParent>。
首先介紹下一些常見的抽象。

列表的資料集合

//列表的資料集合
    protected List<T> dataList = new ArrayList<>();

viewHodler封裝易變的佈局和資料與控制元件的程式碼編寫

   private Class<T> viewHolder;

保留Activity與Fragment,在建構函式中過載了兩種型別,按需使用

   protected Context mContext;
   private Fragment fragment;

常規操作判斷空,獲取資料集大小,更新列表資料

  public boolean isEmpty() {

        return dataList.isEmpty();
    }

    @Override
    public int getItemCount() {
        return dataList.size();
    }

    public void updateData(List<T> data, boolean isClearOld) {
        if (isClearOld) {
            dataList.clear();
        }
        if (data != null && !data.isEmpty()) {
            dataList.addAll(data);
        }
        notifyDataSetChanged();

    }

定義一個抽象類DefineHolder,這個類是需要自己繼承實現的,是我們完成業務的程式設計空間

    public static abstract class DefineHolder<T> extends RecyclerView.ViewHolder implements  FindFather<ApaterParent> {

我們以前類似於getView的程式碼都寫在此處

        public abstract void setData(final T bean, final int position, final ApaterParent myself);

常規程式碼不做解釋

  public void notifyDataSetChanged() {

            if (adapter != null) {
                adapter.notifyDataSetChanged();
            }
        }
        public boolean isLastPosition() {
            return count - 1 == getLayoutPosition();
        }
        public boolean isFirstPosition() {
            return 0 == getLayoutPosition();

        }

ApaterParent是用於解決巢狀adapter中的資料,位置資訊的傳遞,可以想象成一個子到父的連結串列,通過getFather方法可以取到自己的父adapter,可以依次逐級呼叫,getBean,getPosition可以取到需要的資料和位置

 public static abstract class ApaterParent<T> {


        public ApaterParent father;

        public abstract int getPosition();

        public abstract T getBean();

        public ApaterParent getFather() {
            return father;
        }

        public void setFather(ApaterParent father) {
            this.father = father;
        }
    }

onCreateViewHolder 這個方法中可以看到首先根據執行時註解UseLayout取到對應的佈局,隨後通過反射建立一個MyCommonAdapter.DefineHolder例項,例項連結到父類介面卡,返回


 @NonNull
    @Override
    public MyCommonAdapter.DefineHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        UseLayout layout = viewHolder.getAnnotation(UseLayout.class);
        if (layout == null) {
            throw new RuntimeException("請設定佈局註解");
        }
        int layoutRes = 0;
        if (layout != null) {
            layoutRes = layout.layoutRes();
        }
        try {
            Constructor<T> constructor = viewHolder.getConstructor(Context.class, View.class);
            MyCommonAdapter.DefineHolder viewHolder = (MyCommonAdapter.DefineHolder) constructor.newInstance(mContext, mInflater.inflate(layoutRes, null, false));
            viewHolder.admitFather(apaterParent);
            viewHolder.setAdapter(this);
            if (fragment != null) {
                viewHolder.setFragment(fragment);
            }
            return viewHolder;
        } catch (Exception e) {
            LogUtils.d(TAG, "onCreateViewHolder 發生錯誤:", e.toString());
        }
        return null;
    }

onBindViewHolder 負責建立MyCommonAdapter.ApaterParent節點傳入並且將資料和位置傳給實現的setData方法。

  @Override
    public void onBindViewHolder(final @NonNull RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof MyCommonAdapter.DefineHolder) {
            ((MyCommonAdapter.DefineHolder) holder).setCount(dataList.size());
            final T itemBean = dataList.get(position);
            if (itemBean != null) {
                MyCommonAdapter.ApaterParent<T> myself = new MyCommonAdapter.ApaterParent<T>() {

                    @Override
                    public int getPosition() {
                        return position;
                    }

                    @Override
                    public T getBean() {
                        return itemBean;
                    }
                };
                myself(getFather());
                ((MyCommonAdapter.DefineHolder) holder).setData(itemBean, position, myself);

            }
        }

    }

現在需要展示一個九宮格的圖片列表。下面是示範程式碼。MyCommonAdapter建構函式,第一個引數,Fragment或者Activity, 第二個引數自己定義的業務ViewHolder,admitFather方法繫結父類(這段程式碼也是在一個adapter中,單層adapter不需要認爹)。 其他的就是常見程式碼

  List<AlongSceneryDetailBean.DetailListBean> detailList = bean.getDetailList();
        if (detailList != null && !detailList.isEmpty()) {
            MyCommonAdapter imageAdapter = new MyCommonAdapter(getFragment(), AlongSceneryImageItemViewHolder.class);
            imageAdapter.admitFather(myself);
            GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(),3);
            dataRv.setLayoutManager(gridLayoutManager);
            dataRv.setAdapter(imageAdapter);
            imageAdapter.updateData(detailList, true);

        } else {
            dataRv.setVisibility(View.GONE);
        }

UseLayout註解中設定layout,設定業務資料AlongSceneryDetailBean.DetailListBean, ButterKnife繫結控制元件,setData中寫業務邏輯

@UseLayout(layoutRes = R.layout.item_alone_scenery_image)
public class AlongSceneryImageItemViewHolder extends MyCommonAdapter.DefineHolder<AlongSceneryDetailBean.DetailListBean> {


    @Bind(R.id.content_iv)
    ImageView contentIv;

    public AlongSceneryImageItemViewHolder(Context context, View itemView) {
        super(context, itemView);

    }

    @Override
    public void setData(final AlongSceneryDetailBean.DetailListBean bean, int position,final MyCommonAdapter.ApaterParent myself) {
        if(!TextUtils.isEmpty(bean.getImgUrl())){
            Glide.with(getContext()).load(bean.getImgUrl()).into(contentIv);
        }else{
            Glide.with(getContext()).load(R.color.white_pure).into(contentIv);
        }
        contentIv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
           
				//do something
                        }
                    }

                }

            }
        });



    }
}

UseLayout註解中設定layout,設定業務資料AlongSceneryDetailBean.DetailListB