android之實現萬能適配RecyclerView的adapter

現在基本大家都推薦RecyclerView,很少有人使用ListView了,包括我自己也是,已經很久沒用ListView了,所以關於ListView的萬能adapter就不寫了。
每次寫項目的時候,每次遇到RecyclerView都要重新寫一個Adapter,一大堆東西重複寫,麻煩死了,實在是忍不住了,以前懶,總是懶得去搞快捷的adapter,現在項目裏面好多個RecyclerView,馬丹,寫adapter寫吐,所以麻溜麻溜的來寫個萬能adapter了,這種東西網上很多,原理差不多都是重寫ViewHolder和Adapter,主要是使用泛型實現。廢話不多說,擼代碼吧。

注意:我的萬能adapter中集成了下拉刷新、上拉加載更多、另增header的功能,需要與我後面將寫到的萬能下拉刷新、上拉加載更多的RecyclerView搭配使用,如果你不需要下拉刷新和上拉加載更多可以無視其中相關的代碼。

首先,我們知道我們對adapter中控件獲取都是通過ViewHolder來的,所以我們首先要自定義ViewHolder,來完成各個控件的獲取和初始化。正常情況下,我們是這樣用的:

class HistoryGoodsHolder extends RecyclerView.ViewHolder {
        ImageView img;
        TextView name, summary, amount, price;

        public HistoryGoodsHolder(View itemView) {
            super(itemView);
            img = (ImageView) itemView.findViewById(R.id.item_history_rcv_goodsImg);
            name = (TextView) itemView.findViewById(R.id.item_history_rcv_goodsName);
            summary = (TextView) itemView.findViewById(R.id.item_history_rcv_goodsSummary);
            amount = (TextView) itemView.findViewById(R.id.item_history_rcv_goodsAmount);
            price = (TextView) itemView.findViewById(R.id.item_history_rcv_goodsPrice);
        }
    }

現在我們需要實現的是萬能的adapter,所以holder必須實現可定製化,而不是這種固定死的,每個RecyclerView的item佈局不一樣就重新寫個holder,這樣就沒有意義了,所以我們寫的萬能Holder中不能存在真實的控件id,因爲一旦id固定,那麼肯定得重複寫holder,畢竟id不一樣,holder就不一樣了,所以我們可以通過一種方式,那就是findById方法裏面的id不寫死,而是用參數的方式傳進去,這樣可以寫一個通用的Holer,不同的recyclerview可以傳不同的id進去就行了。ViewHolder最終形態:

public class XJJBaseRvHolder extends RecyclerView.ViewHolder {
    private SparseArray<View> mViews;  //view的集合
    private View mConvertView;  //item的佈局
    private Context mContext;  //上下文

    public XJJBaseRvHolder(Context context, View itemView) {
        super(itemView);
        mContext = context;
        mConvertView = itemView;
        mViews = new SparseArray<View>();
    }

    //獲取item的佈局
    public View getItemView(){
        return mConvertView;
    }

    //初始化控件,通過傳進去id來初始化,使用泛型實現傳遞任何類型
    public <T extends View> T getView(int viewId)
    {
        View view = mViews.get(viewId);
        if (view == null)
        {
            view = mConvertView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return (T) view;
    }

    //快捷設置TextView的文本
    public XJJBaseRvHolder setText(int viewId, String text)
    {
        TextView tv = getView(viewId);
        tv.setText(text);
        return this;
    }

    //快捷設置ImageView的圖片
    public XJJBaseRvHolder setSrc(int viewId, int resId)
    {
        ImageView view = getView(viewId);
        view.setImageResource(resId);
        return this;
    }

    //設置控件的點擊事件
    public XJJBaseRvHolder setOnClickListener(int viewId, View.OnClickListener listener)
    {
        View view = getView(viewId);
        view.setOnClickListener(listener);
        return this;
    }

    //設置item的點擊事件
    public XJJBaseRvHolder setItemOnClickListener(View.OnClickListener listener){
        mConvertView.setOnClickListener(listener);
        return this;
    }

ViewHolder搞定,接下來是adapter,ViewHolder是在adapter裏面被引用的,如果我們在adapter裏面實例化holder後傳入id來初始化控件,肯定不行,因爲如果在adapter裏面傳入id,那麼adapter使用了真實的id,那麼id不一樣adapter又得不一樣,還得重複寫了,所以我們不能在adapter裏面傳入id來初始化item的控件,那麼怎麼實現呢?用接口來實現,具體如下:
先寫接口把Holder傳遞出去,然後初始化adapter的時候實現改接口。

public interface ItemDataListener<T> {//接口
        void setItemData(XJJBaseRvHolder holder, T t);
    }

public void setItemDataListener(ItemDataListener listener) {
        itemDataListener = listener;
    }

然後我們數據填充的時候需要先初始化控件的,所以我在onBindViewHolder裏面調用接口,讓初始化和數據填充在外面執行。

@Override
    public void onBindViewHolder(final XJJBaseRvHolder holder, int position) {
        if (getItemViewType(position) == TYPE_HEADER || getItemViewType(position) == TYPE_PULL_TO_REFRESH_HEADER) {//如果是頭部,不做數據填充
            return;
        } else if (getItemViewType(position) == TYPE_FOOTER) {
            return;
        } else {
            if (itemDataListener == null) {
                return;
            }
            itemDataListener.setItemData(holder, datas.get(getRealPosition(holder)));
        }
    }

實現接口並初始化控件填充數據:

adapter.setItemDataListener(new XJJBaseRvAdapter.ItemDataListener<Integer>() {
            @Override
            public void setItemData(final XJJBaseRvHolder holder, Integer integer) {
                TextView textView = holder.getView(R.id.item_index_tv);
                textView.setText(String.valueOf(integer));
            }
});

以上就是控件初始化和數據填充的思路流程,可能講的不是很清楚?水平有限啊,從小語文就不好啊,看不懂的你們可以看鴻洋大神的,這部分思路是借鑑鴻洋大神的。
接下來,就是設正常的header、下拉刷新頭、上拉加載更多footer了。
首先需要3個變量代表四種類型(上面三種+正常數據)。

public static final int TYPE_HEADER = 0;  //正常頭部
public static final int TYPE_PULL_TO_REFRESH_HEADER = 1;  //下拉刷新頭部
public static final int TYPE_NORMAL = 2;  //正常數據
public static final int TYPE_FOOTER = 3;  //上拉footer

然後添加的實現方法:

//添加下拉刷新頭
public void addPullToRefreshHeaderView(View
addPullToRefreshHeaderView) {
        if (headerView != null) return;  //如果已經先添加了headerView,就不能增加下拉頭了
        if (pullToRefreshHeaderView != null || addPullToRefreshHeaderView == null) {
            return;
        }
        this.pullToRefreshHeaderView = addPullToRefreshHeaderView;
        notifyItemInserted(0);
}

//添加頭部佈局(非下拉頭),僅限一個
public void addHeaderView(View addHeaderView) {
        if (addHeaderView == null || headerView != null) {
            return;
        }
        this.headerView = addHeaderView;
        notifyItemInserted(pullToRefreshHeaderView == null ? 0 : 1);
}

//添加footeer
public void addLoadMoreFooterView(View addLoadMoreFooterView) {
        if (loadMoreFooterView != null || addLoadMoreFooterView == null) {
            return;
        }
        this.loadMoreFooterView = addLoadMoreFooterView;
        notifyItemInserted(getItemCount() - 1);
}

然後設置ViewType:

@Override
public int getItemViewType(int position) {
        if (loadMoreFooterView != null && position == getItemCount() - 1) {
            return TYPE_FOOTER;
        }
        if (pullToRefreshHeaderView == null && headerView == null) {
            return TYPE_NORMAL;
        }
        if (pullToRefreshHeaderView == null && headerView != null) {
            if (position == 0) {
                return TYPE_HEADER;
            }
        }
        if (pullToRefreshHeaderView != null && headerView == null) {
            if (position == 0) {
                return TYPE_PULL_TO_REFRESH_HEADER;
            }
        }
        if (pullToRefreshHeaderView != null && headerView != null) {
            if (position == 0) return TYPE_PULL_TO_REFRESH_HEADER;
            if (position == 1) return TYPE_HEADER;
        }
        return TYPE_NORMAL;
}

接下來需要獲取真實的position,因爲正常情況下RecyclerView中position爲0的item跟我們數據data(一般是List類型)的data.get(0)對應,但是由於添加了header所以header的position纔是0,數據對應不上,所以我們得修改下position獲取方式:

//獲取真實的position(與datalist對應,因爲添加了頭部,會使得position和data對應不上)
public int getRealPosition(RecyclerView.ViewHolder holder) {
        int position = holder.getLayoutPosition();
        if (pullToRefreshHeaderView == null) {
            return headerView == null ? position : position - 1;
        } else {
            return headerView == null ? position - 1 : position - 2;
        }
}

然後呢,我們還要修改getItemCount,因爲添加了header和footer,所以Item數量變了:

@Override
public int getItemCount() {
        if (pullToRefreshHeaderView == null) {
            if (headerView == null) {
                return loadMoreFooterView == null ? datas.size() : datas.size() + 1;
            } else {
                return loadMoreFooterView == null ? datas.size() + 1 : datas.size() + 2;
            }
        } else {
            if (headerView == null) {
                return loadMoreFooterView == null ? datas.size() + 1 : datas.size() + 2;
            } else {
                return loadMoreFooterView == null ? datas.size() + 2 : datas.size() + 3;
            }
        }
}

其次是onCreateViewHolder,也要根據不同類型生成不同的holder:

@Override
public XJJBaseRvHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (pullToRefreshHeaderView != null && viewType == TYPE_PULL_TO_REFRESH_HEADER) {//如果是下拉頭
            DisplayMetrics dm = new DisplayMetrics();
            WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            wm.getDefaultDisplay().getMetrics(dm);
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
            pullToRefreshHeaderView.setLayoutParams(layoutParams);
            return new XJJBaseRvHolder(context, pullToRefreshHeaderView);
        }
        if (headerView != null && viewType == TYPE_HEADER) {//如果是正常頭
            DisplayMetrics dm = new DisplayMetrics();
            WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            wm.getDefaultDisplay().getMetrics(dm);
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
            headerView.setLayoutParams(layoutParams);
            return new XJJBaseRvHolder(context, headerView);
        }
        if (loadMoreFooterView != null && viewType == TYPE_FOOTER) {
            DisplayMetrics dm = new DisplayMetrics();
            WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            wm.getDefaultDisplay().getMetrics(dm);
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
            loadMoreFooterView.setLayoutParams(layoutParams);
            return new XJJBaseRvHolder(context, loadMoreFooterView);
        }
        View view = LayoutInflater.from(context).inflate(layoutId, parent, false);
        XJJBaseRvHolder holder = new XJJBaseRvHolder(context, view);
        return holder;
}

可能有的朋友看不明白,其實還是很簡單的,多看兩遍就明白了,我把總的貼出來吧。adapter最終形態:

public class XJJBaseRvAdapter<T> extends RecyclerView.Adapter<XJJBaseRvHolder> {

    protected Context context;
    protected ItemDataListener itemDataListener;
    private View pullToRefreshHeaderView, headerView, loadMoreFooterView;

    protected int layoutId;
    protected List<T> datas;
    public static final int TYPE_HEADER = 0;  //正常頭部
    public static final int TYPE_PULL_TO_REFRESH_HEADER = 1;  //下拉刷新頭部
    public static final int TYPE_NORMAL = 2;  //正常數據
    public static final int TYPE_FOOTER = 3;  //上拉footer

    public XJJBaseRvAdapter(Context context, int layoutId, List<T> datas) {
        this.context = context;
        this.layoutId = layoutId;
        this.datas = datas;
    }

    public void setDatas(List<T> datas) {
        this.datas = datas;
        notifyDataSetChanged();
    }

    //添加下拉刷新頭
    public void addPullToRefreshHeaderView(View addPullToRefreshHeaderView) {
        if (headerView != null) return;  //如果已經先添加了headerView,就不能增加下拉頭了
        if (pullToRefreshHeaderView != null || addPullToRefreshHeaderView == null) {
            return;
        }
        this.pullToRefreshHeaderView = addPullToRefreshHeaderView;
        notifyItemInserted(0);
    }

    //添加頭部佈局(非下拉頭),僅限一個
    public void addHeaderView(View addHeaderView) {
        if (addHeaderView == null || headerView != null) {
            return;
        }
        this.headerView = addHeaderView;
        notifyItemInserted(pullToRefreshHeaderView == null ? 0 : 1);
    }

    //添加footeer
    public void addLoadMoreFooterView(View addLoadMoreFooterView) {
        if (loadMoreFooterView != null || addLoadMoreFooterView == null) {
            return;
        }
        this.loadMoreFooterView = addLoadMoreFooterView;
        notifyItemInserted(getItemCount() - 1);
    }

    public View getPullToRefreshHeaderView(){
        return pullToRefreshHeaderView;
    }

    public View getLoadMoreFooterView(){
        return loadMoreFooterView;
    }

    @Override
    public int getItemViewType(int position) {
        if (loadMoreFooterView != null && position == getItemCount() - 1) {
            return TYPE_FOOTER;
        }
        if (pullToRefreshHeaderView == null && headerView == null) {
            return TYPE_NORMAL;
        }
        if (pullToRefreshHeaderView == null && headerView != null) {
            if (position == 0) {
                return TYPE_HEADER;
            }
        }
        if (pullToRefreshHeaderView != null && headerView == null) {
            if (position == 0) {
                return TYPE_PULL_TO_REFRESH_HEADER;
            }
        }
        if (pullToRefreshHeaderView != null && headerView != null) {
            if (position == 0) return TYPE_PULL_TO_REFRESH_HEADER;
            if (position == 1) return TYPE_HEADER;
        }
        return TYPE_NORMAL;
    }

    //獲取真實的position(與datalist對應,因爲添加了頭部,會使得position和data對應不上)
    public int getRealPosition(RecyclerView.ViewHolder holder) {
        int position = holder.getLayoutPosition();
        if (pullToRefreshHeaderView == null) {
            return headerView == null ? position : position - 1;
        } else {
            return headerView == null ? position - 1 : position - 2;
        }
    }

    public void setItemDataListener(ItemDataListener listener) {
        itemDataListener = listener;
    }

    @Override
    public XJJBaseRvHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (pullToRefreshHeaderView != null && viewType == TYPE_PULL_TO_REFRESH_HEADER) {//如果是下拉頭
            DisplayMetrics dm = new DisplayMetrics();
            WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            wm.getDefaultDisplay().getMetrics(dm);
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
            pullToRefreshHeaderView.setLayoutParams(layoutParams);
            return new XJJBaseRvHolder(context, pullToRefreshHeaderView);
        }
        if (headerView != null && viewType == TYPE_HEADER) {//如果是正常頭
            DisplayMetrics dm = new DisplayMetrics();
            WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            wm.getDefaultDisplay().getMetrics(dm);
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
            headerView.setLayoutParams(layoutParams);
            return new XJJBaseRvHolder(context, headerView);
        }
        if (loadMoreFooterView != null && viewType == TYPE_FOOTER) {
            DisplayMetrics dm = new DisplayMetrics();
            WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            wm.getDefaultDisplay().getMetrics(dm);
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(dm.widthPixels, ViewGroup.LayoutParams.WRAP_CONTENT);
            loadMoreFooterView.setLayoutParams(layoutParams);
            return new XJJBaseRvHolder(context, loadMoreFooterView);
        }
        View view = LayoutInflater.from(context).inflate(layoutId, parent, false);
        XJJBaseRvHolder holder = new XJJBaseRvHolder(context, view);
        return holder;
    }

    @Override
    public void onBindViewHolder(final XJJBaseRvHolder holder, int position) {
        if (getItemViewType(position) == TYPE_HEADER || getItemViewType(position) == TYPE_PULL_TO_REFRESH_HEADER) {//如果是頭部,不做數據填充
            return;
        } else if (getItemViewType(position) == TYPE_FOOTER) {
            return;
        } else {
            if (itemDataListener == null) {
                return;
            }
            itemDataListener.setItemData(holder, datas.get(getRealPosition(holder)));
        }
    }

    @Override
    public int getItemCount() {
        if (pullToRefreshHeaderView == null) {
            if (headerView == null) {
                return loadMoreFooterView == null ? datas.size() : datas.size() + 1;
            } else {
                return loadMoreFooterView == null ? datas.size() + 1 : datas.size() + 2;
            }
        } else {
            if (headerView == null) {
                return loadMoreFooterView == null ? datas.size() + 1 : datas.size() + 2;
            } else {
                return loadMoreFooterView == null ? datas.size() + 2 : datas.size() + 3;
            }
        }
    }

    public interface ItemDataListener<T> {
        void setItemData(XJJBaseRvHolder holder, T t);
    }

}

用法:

XJJBaseRvAdapter adapter = new XJJBaseRvAdapter(this, R.layout.item_rcv, mdatas);//傳入item佈局
adapter.setItemDataListener(new XJJBaseRvAdapter.ItemDataListener<Integer>() {
            @Override
            public void setItemData(final XJJBaseRvHolder holder, Integer integer) {
                TextView textView = holder.getView(R.id.item_index_tv);//初始化控件
                textView.setText(String.valueOf(integer));
            }
        });

很簡單的,初始化adapter時傳入item的佈局即可,然後實現接口在裏面做數據處理即可。

如果大家有不懂的可以留言或者看鴻洋大神的相關博客,他那裏比較詳細,我這裏需要是爲了給後面的文章(萬能的下拉刷新、上拉加載更多)做個鋪墊,因爲後面的跟這個萬能adapter配合用的,以後用起來都方便。有不當之處還望見諒。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章