android 打造真正的下拉刷新上拉加载recyclerview(二):添加删除头尾部

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/anyfive/article/details/53022682

转载请注明出处:http://blog.csdn.net/anyfive/article/details/53022682

前言

在上一篇文章中,我们介绍了下拉刷新上拉加载RecyclerView的使用,从这篇开始,我将对这个项目的具体实现详细介绍,这篇首先介绍添加删除头尾部的实现。

大家都知道recyclerview并不能像listview一样,可以直接使用addHeaderView和addFooterView添加头部和尾部,而在实际的项目中,我们常常需要实现这些功能。既然recyclerview不提供,那我们就自己写一个呗。

关于给recyclerview添加头部和尾部,网上已有非常多的介绍,比如我们的鸿洋大神就写过一篇 《 Android 优雅的为RecyclerView添加HeaderView和FooterView》

但是啊,添加有了,删除头尾部的介绍网上比较少。看过鸿洋大神这篇文章的同学想必都知道,给recyclerview添加头尾部,其实就是 在adapter中使用键值数组对存储头部和尾部,addHeaderView其实就是往头部的键值对数组中添加view。那么removeHeaderView呢?当然就是从头部的键值对数组中删除view啦!!没错,就是这么简单。

这时候,有同学把网上的代码拷下来,开始在adapter中添加removeHeaderView方法,恩,完美。添加View0,添加View1,删除View1,哦!删除成功,添加View2,哎?怎么添加的是View1??我的View2呢?

不要问我怎么知道的,因为我就这样干过。

添加删除头尾部

那么我们还是先一步步来介绍下头尾部的实现吧,recyclerview的adapter需要实现以下几个方法:

  • 创建ViewHolder:onCreateViewHolder(ViewGroup parent, int viewType);
  • 绑定数据:onBindViewHolder(RecyclerView.ViewHolder holder, int position);
  • 获得item的总数:getItemCount();

恩,跟ListView还是有点不一样的。在ListView的时候,当我们需要展示几种类型的item时,我们会使用到一个方法:

  • 获得item的类型:getItemViewType(int position);

幸运的是,recyclerview中也提供这个方法,有这个方法就好办了,我们把头尾部当成是一种item不就好了嘛,用键值对数组保存头尾部,类型type作为key,view作为value。

因此思路就是:

  1. 使用键值对数组(ArrayList、HashMap、SparseArrayCompat等)保存头尾部
  2. 在getItemViewType方法中,判断是否头尾部,是的话返回对应的key,也就是type
  3. 在onCreateViewHolder中,判断是否是头尾部,是的话返回对应的ViewHolder
  4. 在onBindViewHolder中,判断是否是头尾部,是的话不处理

大概就是这样,下面我们看看这部分代码,一个一个来:

首先,键值对保存头尾部:

protected SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
protected SparseArrayCompat<View> mFooterViews = new SparseArrayCompat<>();

然后,getItemCount方法,返回item数量+头尾部总数:

@Override
public int getItemCount() {
    return getRealItemCount()+getHeadersCount()+getFootersCount();
}

再然后,getItemViewType方法:

@Override
public int getItemViewType(int position) {
    //如果是头部,返回对应的type
    if (isHeaderPosition(position)) {
        return mHeaderViews.keyAt(position);
    }
    //如果是尾部,返回对应的type
    if (isFooterPosition(position)) {
        return mFooterViews.keyAt(position - getHeadersCount() - getRealItemCount());
    }
    //如果是item,返回真正的adapter的getItemViewType方法
   return mRealAdapter.getItemViewType(position - getHeadersCount());
}

再再然后,onCreateViewHolder方法:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//        如果是头部
    if (isHeaderType(viewType)) {
        int headerPosition = mHeaderViews.indexOfKey(viewType);
        View headerView = mHeaderViews.valueAt(headerPosition);
        return createHeaderAndFooterViewHolder(headerView);
    }
//        如果是尾部
    if (isFooterType(viewType)) {
        int footerPosition = mFooterViews.indexOfKey(viewType);
        View footerView = mFooterViews.valueAt(footerPosition);
        return createHeaderAndFooterViewHolder(footerView);
    }
    return mRealAdapter.onCreateViewHolder(parent, viewType);
}

再再再然后,onBindViewHolder方法:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (isHeaderPosition(position) || isFooterPosition(position)) {

    } else {
        mRealAdapter.onBindViewHolder(holder, realPosition);
    }
}

再再再再然后,添加addHeaderView和addFooterView方法:

public void addHeaderView(View view) {
    mHeaderViews.put(BASE_ITEM_TYPE_HEADER++, view);
    notifyDataSetChanged();
}

public void addFooterView(View view) {
    mFooterViews.put(BASE_ITEM_TYPE_FOOTER ++,view);
    notifyDataSetChanged();
}

注意:这里如果你使用了网上的代码,比如鸿洋大神的:

private static final int BASE_ITEM_TYPE_HEADER = 100000;

public void addHeaderView(View view)
{
    mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
}

如果你是这样写的,那就会造成前言部分说了View2没有添加成功的问题了。

那么是为什么呢?我们来推演一番:

  1. 添加View0,type是100000;
  2. 添加View1,type是100001;
  3. 删除View1;
  4. 添加View2,type是100001;

啊哦,getItemViewType拿到的type还是原来view1的type,那自然View2会变成View1了。那怎么办呢?我们用自增不就好了嘛。

恩,接着上面,继续加上删除头尾部的方法:

public void removeHeaderView(View view) {
    int index = mHeaderViews.indexOfValue(view);
    if (index < 0) return;
    mHeaderViews.removeAt(index);
    notifyDataSetChanged();
}

public void removeFooterView(View view) {
    int index = mFooterViews.indexOfValue(view);
    if (index < 0) return;
    mFooterViews.removeAt(index);
    notifyDataSetChanged();
}

恩,这下应该没问题了。

等等!LinearLayoutManager是没问题了,GridLayoutManager和StaggeredGridLayoutManager呢?其实想想就知道,肯定会有问题的嘛~~问题就是头部和尾部的宽度会和item一样长,而不是我们希望的填满recyclerview的宽度,每个头尾部单独占一行。

当然解决办法鸿洋大大也说过了,网上也有一大堆:

/**解决GridLayoutManager问题*/
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        mRealAdapter.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    if (isHeaderPosition(position) || isFooterPosition(position))
                        return gridLayoutManager.getSpanCount();
                    return 1;
                }
            });
        }

    }

    /**解决瀑布流布局问题*/
    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        mRealAdapter.onViewAttachedToWindow(holder);
        int position = holder.getLayoutPosition();
        if (isHeaderPosition(position) || isFooterPosition(position)) {
            ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) {
                StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) lp;
                layoutParams.setFullSpan(true);
            }
        }
    }

搞定~

好了,再看下效果图吧:

头尾部

Grid头尾部

Staggred头尾部

当然,我们要做的是封装一个可以添加删除头尾部的RecyclerView,这算哪门子的封装啊?我们当然要把这些个方法封装好一点,使用起来也爽嘛。

一提起封装,我脑子里就冒出个东西:泛型

泛型真是个好东西啊,那个灵活,那个爽,啧啧啧,嘿嘿……好了,关于泛型这里就不详细介绍了,我们还是来说下封装吧。(一本正经脸)

在开始封装之前,先问问自己:“如果是你使用,你想怎么用?”

“那还用说?当然是还和原来一样setAdapter,像listview一样直接用addHeaderView啊!”

“恩,没问题,开始吧”

  1. 继承RecyclerView,重写setAdapter方法,添加addHeaderView等方法;
  2. 用户设置的adapter,作为真正的adapter(mRealAdapter)传入头尾部Adapter;

先看看第一步:

protected HeaderAndFooterAdapter mAdapter;
protected Adapter mRealAdapter;

@Override
public void setAdapter(Adapter adapter) {
    mRealAdapter = adapter;
    if (adapter instanceof HeaderAndFooterAdapter) {
        mAdapter = (HeaderAndFooterAdapter) adapter;
    }
    else {
        mAdapter = new HeaderAndFooterAdapter(getContext(),adapter);
    }
    super.setAdapter(mAdapter);
}

public void addHeaderView(View view) {
    if (null == view) {
        throw new IllegalArgumentException("the view to add must not be null !");
    } else if (mAdapter == null) {
        throw new IllegalStateException("u must set a adapter first !");
    } else {
        mAdapter.addHeaderView(view);
    }
}

public void addFooterView(View view) {
    if (null == view) {
        throw new IllegalArgumentException("the view to add must not be null !");
    } else if (mAdapter == null) {
        throw new IllegalStateException("u must set a adapter first !");
    } else {
        mAdapter.addFooterView(view);
    }
}

public void removeHeaderView(View view) {
    if (null == view) {
        throw new IllegalArgumentException("the view to remove must not be null !");
    } else if (mAdapter == null) {
        throw new IllegalStateException("u must set a adapter first !");
    } else {
        mAdapter.removeHeaderView(view);
    }
}

public void removeFooterView(View view) {
    if (null == view) {
        throw new IllegalArgumentException("the view to remove must not be null !");
    } else if (mAdapter == null) {
        throw new IllegalStateException("u must set a adapter first !");
    } else {
        mAdapter.removeFooterView(view);
    }
}

完美,再看看第二步,使用泛型就好啦~

public class HeaderAndFooterAdapter<T extends RecyclerView.Adapter> extends RecyclerView.Adapter {
    protected T mRealAdapter;
    protected Context mContext;

    public HeaderAndFooterAdapter(Context mContext, T mRealAdapter) {
        super();
        this.mContext = mContext;
        this.mRealAdapter = mRealAdapter;
    }

    public T getRealAdapter() {
        return mRealAdapter;
    }

    //其他方法上面已介绍,这里就省略了。

}

好了,这样就可以了。使用的时候,你还是按照你原来的方式setAdapter,不用管其他,想addHeaderView就直接调HeaderAndFooterRecyclerView的addHeaderView方法就好啦,和ListView是一样样的。

添加点击事件

本来添加点击事件我不想写了,因为网上太多太多例子了,但想到都说了这么多了,再说一下也没什么啦。

其实添加点击事件就是给每个item添加一个点击监听嘛,然后传入position就够了,注意:因为我们添加了头部和尾部,position需要处理一下,不然到时候对用户来说,你的position就是不准的。

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
   if (isHeaderPosition(position) || isFooterPosition(position)) {

    } else {
        final int realPosition = position - getHeadersCount();
        mRealAdapter.onBindViewHolder(holder, realPosition);
        if (mOnItemClickListener != null) {
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mOnItemClickListener.OnItemClick(realPosition);
                }
            });
        }
        if (mOnItemLongClickListener != null) {
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    return mOnItemLongClickListener.onItemLongClick(realPosition);
                }
            });
        }
    }
}

这样就可以了,点击和长按就加上去了,接下来跟addHeaderView一样,加几个方法用来调用就可以了。

在adapter中加入:

protected OnItemClickListener mOnItemClickListener;
protected OnItemLongClickListener mOnItemLongClickListener;

public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
    this.mOnItemClickListener = onItemClickListener;
}

public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
    this.mOnItemLongClickListener = onItemLongClickListener;
}

在HeaderAndFooterRecyclerView中加入:

 public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
    if (null == mAdapter) {
        throw new IllegalStateException("u must set a adapter first !");
    } else {
        mAdapter.setOnItemClickListener(onItemClickListener);
    }
}

public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
    if (null == mAdapter) {
        throw new IllegalStateException("u must set a adapter first !");
    } else {
        mAdapter.setOnItemLongClickListener(onItemLongClickListener);
    }
}

搞定,至此,PTLRecyclerView的头尾部相关就介绍完了,其实PTLRecyclerView的HeaderAndFooterRecyclerView中还封装了EmptyView的实现,这里就不介绍了,有兴趣的同学可以去看看源码。

源码地址:https://github.com/whichname/PTLRecyclerView

有意见或建议或疑问等等,欢迎提出~~

传送门:

android 打造真正的下拉刷新上拉加载recyclerview(一):使用

android 打造真正的下拉刷新上拉加载recyclerview(三):下拉刷新上拉加载

android 打造真正的下拉刷新上拉加载recyclerview(四):自动加载和其他封装

猜你喜欢

转载自blog.csdn.net/anyfive/article/details/53022682
今日推荐