封裝RecyclerView,實現新增頭部和底部

鋸齒流沙發表於2017-12-26

我們都知道ListView具有新增頭部和新增底部的方法,但是RecyclerView並沒有這樣子的方法。所以RecyclerView是不能新增底部和頭部的,但是能不能仿造ListView來實現RecyclerView新增頭部和底部呢?答案當然是可行的。 首先看下實現的效果:

fengzhuang.png

public class WrapMyRecyclerView extends RecyclerView {

    private Adapter mAdapter;
    private ArrayList<View> mHeaderViewInfos = new ArrayList<>();//儲存headerView
    private ArrayList<View> mFooterViewInfos = new ArrayList<>();//儲存footerView

    public WrapMyRecyclerView(Context context) {
        super(context);
    }

    public WrapMyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public WrapMyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * 新增headerView
     * @param v
     */
    public void addHeaderView(View v) {
        mHeaderViewInfos.add(v);
        // Wrap the adapter if it wasn't already wrapped.
        if (mAdapter != null) {
            if (!(mAdapter instanceof RecyclerHeaderViewListAdapter)) {
                wrapHeaderListAdapterInternal();
            }
        }
    }

    /**
     * 新增一個footerView
     * @param v
     */
    public void addFooterView(View v) {
        mFooterViewInfos.add(v);
        // Wrap the adapter if it wasn't already wrapped.
        if (mAdapter != null) {
            if (!(mAdapter instanceof RecyclerHeaderViewListAdapter)) {
                wrapHeaderListAdapterInternal();
            }
        }
    }

    /**
     * 設定一個Adapter
     * @param adapter
     */
    @Override
    public void setAdapter(Adapter adapter) {

        if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
            mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }
        super.setAdapter(mAdapter);
    }

    private void wrapHeaderListAdapterInternal() {
        mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter);
    }

    /**
     * 新建一個RecyclerHeaderViewListAdapter物件
     * 最終的adapter實現它
     * @param headerViewInfos
     * @param footerViewInfos
     * @param adapter
     * @return
     */
    protected RecyclerHeaderViewListAdapter wrapHeaderListAdapterInternal(
            ArrayList<View> headerViewInfos,
            ArrayList<View> footerViewInfos,
            Adapter adapter) {
        return new RecyclerHeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter);
    }

}

複製程式碼

這就是封裝的RecyclerView,裡面主要有三個方法addHeaderView、 addFooterView和重寫的setAdapter。這裡的唯一的思想就是偷樑換柱,當我們新增頭部、尾部或者設定adapter時,真正的adapter並不是我們傳入的adapter,而是重新new 了一個RecyclerHeaderViewListAdapter。這才是RecyclerView最終設定的adapter。其實我們看到ListView也是通過這樣子的思想來新增頭部和尾部的。

addHeaderView.png

這就是ListView的addHeaderView方法,它會偷偷的建立HeaderViewListAdapter這個adapter。最終新增的header和footer在HeaderViewListAdapter裡面實現。

public class RecyclerHeaderViewListAdapter extends RecyclerView.Adapter {

    private final ArrayList<View> mHeaderViewInfos;//儲存headerview資料
    private final ArrayList<View> mFooterViewInfos;//儲存footerView資料
    private RecyclerView.Adapter mAdapter;         //使用者自己構造的adapter
    private static final int RECYCLER_HEADER_VIEW = 0x001;//headerview型別
    private static final int RECYCLER_FOOTER_VIEW = 0x002;//footerView型別


    /**
     * 構造方法
     * 初始化
     * @param headerViewInfos
     * @param footerViewInfos
     * @param adapter
     */
    public RecyclerHeaderViewListAdapter(ArrayList<View> headerViewInfos,
                                         ArrayList<View> footerViewInfos,
                                         RecyclerView.Adapter adapter) {

        mAdapter = adapter;
        if (headerViewInfos == null) {
            mHeaderViewInfos = new ArrayList<>();
        } else {
            mHeaderViewInfos = headerViewInfos;
        }

        if (footerViewInfos == null) {
            mFooterViewInfos = new ArrayList<>();
        } else {
            mFooterViewInfos = footerViewInfos;
        }
    }

    /**
     * 根據getItemViewType返回的條目型別
     * 建立不同的itemview
     * 傳入的adapter,回撥它的onCreateViewHolder即可
     * @param parent
     * @param viewType
     * @return
     */
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == RECYCLER_HEADER_VIEW){
            return new HeaderViewLayout(mHeaderViewInfos.get(0));
        }else if (viewType == RECYCLER_FOOTER_VIEW){
            return new HeaderViewLayout(mFooterViewInfos.get(0));
        }
        return mAdapter.onCreateViewHolder(parent,viewType);
    }

    /**
     * 繫結資料
     * headerview和footerview不需要繫結資料,直接return即可
     * 傳入的adapter需要回撥它的onBindViewHolder即可
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        //header
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return ;
        }
        //adapter body
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if (mAdapter != null) {
            adapterCount = mAdapter.getItemCount();
            if (adjPosition < adapterCount) {
                mAdapter.onBindViewHolder(holder, adjPosition);
                return ;
            }
        }
        //footer
    }

    /**
     * 返回條目的型別
     * 傳入的adapter,回撥它的getItemViewType即可
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        // Header
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return RECYCLER_HEADER_VIEW;
        }
        // Adapter
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if (mAdapter != null) {
            adapterCount = mAdapter.getItemCount();
            if (adjPosition < adapterCount) {
                return mAdapter.getItemViewType(position);
            }
        }
        // Footer (off-limits positions will throw an IndexOutOfBoundsException)
        return RECYCLER_FOOTER_VIEW;
    }

    /**
     * 總條目即:footerview的條目+headerview的條目+穿入的adapter條目
     * @return
     */
    @Override
    public int getItemCount() {
        if (mAdapter != null) {
            return getFootersCount() + getHeadersCount() + mAdapter.getItemCount();
        } else {
            return getFootersCount() + getHeadersCount();
        }
    }

    /**
     * 獲取headerview的條目
     * @return
     */
    public int getHeadersCount() {
        return mHeaderViewInfos.size();
    }

    /**
     * 獲取footervie的條目
     * @return
     */
    public int getFootersCount() {
        return mFooterViewInfos.size();
    }

    /**
     * 這是footerview和headerview的ViewHolder需要
     * 這裡只是提供一個構造器即可,實際上用處不大
     */
    private static class HeaderViewLayout extends RecyclerView.ViewHolder{

        public HeaderViewLayout(View itemView) {
            super(itemView);
        }
    }

}

複製程式碼

這是實現新增header、footer和傳入adapter的RecyclerHeaderViewListAdapter。具體的邏輯都在檔案的註釋裡面有說明。邏輯是仿造ListView的HeaderViewListAdapter來實現的。 其實就是建立一個adapter,然後根據不同的條目型別來建立條目和繫結條目的資料即可。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.wrap.recycler.WrapRecyclerViewActivity">

    <com.lwj.wrap.recycler.WrapMyRecyclerView
        android:id="@+id/wrap_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

複製程式碼

這是佈局檔案

public class WrapRecyclerViewActivity extends AppCompatActivity {
    private WrapMyRecyclerView mRecyclerView;
    private MyAdapter mMyAdapter;
    private List<String> mList01 = new ArrayList<>();
    private static final int WC = ViewGroup.LayoutParams.WRAP_CONTENT;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_wrap_recycler_view);
        getData();
        mRecyclerView = (WrapMyRecyclerView) this.findViewById(R.id.wrap_recyclerview);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        //加入headerView
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(WC,WC);
        ImageView headerView = new ImageView(this);
        headerView.setImageResource(R.drawable.timg);
        headerView.setLayoutParams(params);
        mRecyclerView.addHeaderView(headerView);
        //設定adapter
        mMyAdapter = new MyAdapter(this,mList01);
        mRecyclerView.setAdapter(mMyAdapter);
        //加入footerView
        params = new ViewGroup.LayoutParams(WC,WC);
        ImageView footerView = new ImageView(this);
        footerView.setImageResource(R.drawable.hhfj);
        footerView.setLayoutParams(params);
        mRecyclerView.addFooterView(footerView);
        mMyAdapter.notifyDataSetChanged();

    }
    public void getData() {
        for (int i = 0; i < 3; i++) {
            String data ="adapter...." + i;
            mList01.add(data);
        }
    }
}

複製程式碼

使用直接呼叫addHeaderView、addFooterView即可。

除了這種方式來實現addHeaderView和addFooterView,另外一種方式就是封裝Adapter來實現,原理還是保持不變:根據不同的條目型別來建立條目和繫結條目的資料。

public class MyCircleRecycViewAdapter extends RecyclerView.Adapter {
    public List<CircleInfo.CirclePageInfo> infos = null;
    private Context mContext;
    private ListViewImgLoader mLoader;
    private View VIEW_FOOTER;//尾部
    private View VIEW_HEADER;//頭部
    //Type
    private int TYPE_NORMAL = 1000;
    private int TYPE_HEADER = 1001;
    private int TYPE_FOOTER = 1002;
    private int tagType = TYPE_NORMAL;

    public MyCircleRecycViewAdapter(Context context,List<CircleInfo.CirclePageInfo> datas) {
        this.infos = datas;
        this.mContext = context;
        mLoader = new ListViewImgLoader();
        mLoader.setMemoryCacheSize(1024 * 1024);
        mLoader.setVisibleItemCount(12);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_FOOTER) {
            tagType = TYPE_FOOTER;
            return new MyCircleItemHolder(VIEW_FOOTER);
        } else if (viewType == TYPE_HEADER) {
            tagType = TYPE_HEADER;
            return new MyCircleItemHolder(VIEW_HEADER);
        } else {
            tagType = TYPE_NORMAL;
            View view = LayoutInflater.from(mContext).inflate(R.layout.circle_gridview_items, parent,false);
            return new MyCircleItemHolder(view);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (!isHeaderView(position) && !isFooterView(position)) {
            if (haveHeaderView()) position--;
            MyCircleItemHolder viewHolder = (MyCircleItemHolder)holder;
            CircleInfo.CirclePageInfo mInfo = infos.get(position);
            setData(viewHolder,mInfo);
        }
    }

    @Override
    public int getItemCount() {
        int count = (infos == null ? 0 : infos.size());
        if (VIEW_FOOTER != null) {
            count++;
        }
        if (VIEW_HEADER != null) {
            count++;
        }
        return count;
    }

    @Override
    public int getItemViewType(int position)
    {
        if (isHeaderView(position)) {
            return TYPE_HEADER;
        } else if (isFooterView(position)) {
            return TYPE_FOOTER;
        } else {
            return TYPE_NORMAL;
        }
    }
    public void addHeaderView(View headerView) {
        if (haveHeaderView()) {
            throw new IllegalStateException("hearview has already exists!");
        } else {
            //避免出現寬度自適應
            ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(Utils.getRealPixel(30), Utils.getRealPixel(230));
            headerView.setLayoutParams(params);
            VIEW_HEADER = headerView;
            notifyItemInserted(0);
        }
    }
    public void addFooterView(View footerView) {
        if (haveFooterView()) {
            throw new IllegalStateException("footerView has already exists!");
        } else {
            ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            footerView.setLayoutParams(params);
            VIEW_FOOTER = footerView;
//            ifGridLayoutManager();
            notifyItemInserted(getItemCount() - 1);
        }
    }

    private boolean haveHeaderView() {
        return VIEW_HEADER != null;
    }

    public boolean haveFooterView() {
        return VIEW_FOOTER != null;
    }

    private boolean isHeaderView(int position) {
        return haveHeaderView() && position == 0;
    }

    private boolean isFooterView(int position) {
        return haveFooterView() && position == getItemCount() - 1;
    }


    private void setData(final MyCircleItemHolder viewHolder, final CircleInfo.CirclePageInfo mInfo) {
        if(mInfo == null || mInfo == viewHolder.tagInfo)
        {
            return;
        }else{
            viewHolder.tagInfo = mInfo;
            if(!StrUtils.isEnpty(mInfo.thread_unread)){
                if(mInfo.thread_unread.equals("0")){
                    viewHolder.threaduUnreadTv.setVisibility(View.INVISIBLE);
                }else{
                    viewHolder.threaduUnreadTv.setVisibility(View.VISIBLE);
                    viewHolder.threaduUnreadTv.setText(mInfo.thread_unread);
                }
            }else{
                viewHolder.threaduUnreadTv.setVisibility(View.INVISIBLE);
            }

            if (!TextUtils.isEmpty(viewHolder.tagUrl)){
                if(mInfo.circle_img_path != null && !(mInfo.circle_img_path.equals(viewHolder.tagUrl))){
                    setImage(viewHolder.mImageView,mInfo.circle_img_path);
                }
            }else{
                setImage(viewHolder.mImageView,mInfo.circle_img_path);
            }
            if (!TextUtils.isEmpty(mInfo.circle_img_path)){
                viewHolder.tagUrl = mInfo.circle_img_path;
            }
            if(mInfo.circleName != null){
                if(mInfo.circleName.length() > 5){
                    String s = mInfo.circleName.substring(0,5) + "...";
                    viewHolder.textView.setText(s);
                }else{
                    viewHolder.textView.setText(mInfo.circleName);
                }
            }

        }
    }

    private void setImage(final ColorFilterImageView mImageView, final String imgUrl){
        mImageView.setBackgroundColor(0xffadadad);
        mImageView.setImageBitmap(null);
        if(!TextUtils.isEmpty(imgUrl))
        {
            mLoader.loadImage(mImageView.hashCode(), imgUrl, 300, new DnImg.OnDnImgListener()
            {

                @Override
                public void onProgress(String url, int downloadedSize, int totalSize)
                {
                    // TODO Auto-generated method stub

                }

                @Override
                public void onFinish(String url, String file, Bitmap bmp)
                {
                    if(url.equals(imgUrl))
                    {
                        mImageView.setImageBitmap(bmp);
                    }
                }
            });
        }else{
            mImageView.setBackgroundColor(0xffadadad);
            mImageView.setImageBitmap(null);
        }
    }


    class MyCircleItemHolder extends RecyclerView.ViewHolder{
        private ColorFilterImageView mImageView;
        private TextView textView;
        private CircleInfo.CirclePageInfo tagInfo;
        private String tagUrl;
        private TextView threaduUnreadTv;

        public MyCircleItemHolder(final View itemView) {
            super(itemView);
            if(tagType ==  TYPE_NORMAL){
                mImageView = (ColorFilterImageView)itemView.findViewById(R.id.quan_icon);
                mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                textView = (TextView)itemView.findViewById(R.id.quan_name);
                threaduUnreadTv = (TextView)itemView.findViewById(R.id.quan_num);
                mImageView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (mListener != null){
                            mListener.oncliItem(itemView,tagInfo,threaduUnreadTv);
                        }
                    }
                });
            }

        }
    }
    public OnclickMyCircleItemListener mListener;
    public void setOnclickMyCircleItemListener(OnclickMyCircleItemListener l){
        this.mListener = l;
    }
    public interface OnclickMyCircleItemListener{
        void oncliItem(View view,CircleInfo.CirclePageInfo info,View threadNumTv);
    }

    public void pauseLoader(){
        if(mLoader != null)
        {
            mLoader.pause();
        }
    }
    public void resumeLoader(){
        if(mLoader != null)
        {
            mLoader.resume();
        }
    }
    public void closeLoader(){
        if(mLoader != null)
        {
            mLoader.close();
        }
    }

}

複製程式碼

相關文章