前言
装饰设计模式系列文章
装饰设计模式(一) - 吃饭小示例
装饰设计模式(二) - RecyclerView添加头部和底部
装饰设计模式(三) - 源码中用到的装饰设计模式
1. 概述
上一节我们讲解了装饰设计模式的定义、及如何去写装饰设计模式,并且也写了一个小的实例 —— 吃饭小示例,那么这一节我们就采用装饰设计模式来分析下 给RecyclerView添加头部和底部
2. RecyclerView 不支持添加头部和底部
其实 RecyclerView是不支持头部和底部的View对象的添加,但是ListView是支持的。我们必须要想办法解决这个问题,当然网上的解决办法有很多,但是我个人所赞同的就是采用装饰设计模式 添加头部和底部,那么接下来我们就首先来看下ListView它为什么可以添加头部和底部;
3. ListView为什么可以添加头部和底部?
- ListView源码中的 addHeaderView()方法,里边有 HeaderViewListAdapter 这个类,这个类是ListView源码给我们写好的,可以添加头部和底部;
- 而RecyclerView是没有写这个类,所以 RecyclerView不支持手动添加 头部和底部,但是我们完全可以参照 ListView中的这个类的写法,来 实现 RecyclerView添加 头部和底部;
- HeaderViewListAdapter这个类也是采用的是 装饰设计模式,让 ListAdapter 变得更加强大;
public void addHeaderView(View v, Object data, boolean isSelectable) {
final FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
mAreAllItemsSelectable &= isSelectable;
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewListAdapter)) {
wrapHeaderListAdapterInternal();
}
}
}
4. 具体代码如下
1>:封装 WrapRecyclerView 替代 原始的 RecyclerView
/**
* Email: [email protected]
* Created by Novate 2018/5/12 18:47
* Version 1.0
* Params:
* Description: 封装的 WrapRecyclerView,替代 原始的 RecyclerView
*/
public class WrapRecyclerView extends RecyclerView {
private WrapRecyclerAdapter mAdapter ;
public WrapRecyclerView(Context context) {
super(context);
}
public WrapRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public WrapRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void setAdapter(Adapter adapter) {
mAdapter = new WrapRecyclerAdapter(adapter) ;
super.setAdapter(adapter);
}
/**
* 添加头部
*/
public void addHeaderView(View view){
// 必须是:在设置 setAdapter之后,才能够去设置头部和底部
if (mAdapter != null){
mAdapter.addHeaderView(view);
}
}
/**
* 添加底部
*/
public void addFooterView(View view){
if (mAdapter != null){
mAdapter.addFooterView(view);
}
}
/**
* 移除头部
*/
public void removeHeaderView(View view){
if (mAdapter != null){
mAdapter.removeHeaderView(view);
}
}
/**
* 移除底部
*/
public void removeFooterView(View view){
if (mAdapter != null){
mAdapter.removeFooterView(view);
}
}
}
2>:装饰设计模式的 RecyclerView.Adapter
/**
* Email: [email protected]
* Created by Novate 2018/5/12 17:25
* Version 1.0
* Params:
* Description: 装饰设计模式的 RecyclerView.Adapter - 我们对其进行功能扩展,使其能够支持头部和底部的添加
*/
public class WrapRecyclerAdapter extends RecyclerView.Adapter{
// 原来的 RecyclerView.Adapter,并不支持头部和底部的添加
private final RecyclerView.Adapter mRealAdapter ;
ArrayList mHeaderViews;
ArrayList mFooterViews;
public WrapRecyclerAdapter(RecyclerView.Adapter adapter){
this.mRealAdapter = adapter ;
// 只要MainActivity中点击 item条目 调用了 notifyDataSetChanged,
// 这里就会收到通知,然后在这里也调用 notifyDataSetChanged方法,就能够删除item了
mRealAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
notifyDataSetChanged();
}
});
mHeaderViews = new ArrayList() ;
mFooterViews = new ArrayList<>() ;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int position) {
// 现在问题就是:如果想知道是哪个部分,就必须知道 position,也就是位置,但是目前只有 viewType
// 头部返回 头部的ViewHolder
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return createHeaderFooterViewHolder(mHeaderViews.get(position));
}
// mRealAdapter返回 mRealAdapter的ViewHolder
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mRealAdapter != null) {
adapterCount = mRealAdapter.getItemCount();
if (adjPosition < adapterCount) {
// 直接传 position ,不兼容 万能适配多布局条目
return mRealAdapter.onCreateViewHolder(parent,mRealAdapter.getItemViewType(adjPosition));
}
}
// 底部返回 底部的ViewHolder
return createHeaderFooterViewHolder(mFooterViews.get(adjPosition - adapterCount));
}
@Override
public int getItemViewType(int position) {
// 把 位置postion 作为 viewType
return position;
}
public int getHeadersCount() {
return mHeaderViews.size();
}
public int getFootersCount() {
return mFooterViews.size();
}
private RecyclerView.ViewHolder createHeaderFooterViewHolder(View view) {
return new RecyclerView.ViewHolder(view){};
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
// 头部和底部是不需要处理的,只有中间的 mRealAdapter需要处理
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return ;
}
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mRealAdapter != null) {
adapterCount = mRealAdapter.getItemCount();
if (adjPosition < adapterCount) {
mRealAdapter.onBindViewHolder(holder,position);
}
}
}
/**
* 总共返回有多少条 = 头部条数 + 真实的Adapter条数 + 底部条数
*/
@Override
public int getItemCount() {
return mHeaderViews.size() + mRealAdapter.getItemCount() + mFooterViews.size();
}
/**
* 添加头部
*/
public void addHeaderView(View view){
if (!mHeaderViews.contains(view)){
mHeaderViews.add(view) ;
notifyDataSetChanged();
}
}
/**
* 添加底部
*/
public void addFooterView(View view){
if (!mFooterViews.contains(view)){
mFooterViews.add(view) ;
notifyDataSetChanged();
}
}
/**
* 移除头部
*/
public void removeHeaderView(View view){
if(mHeaderViews.contains(view)){
mHeaderViews.remove(view) ;
notifyDataSetChanged();
}
}
/**
* 移除底部
*/
public void removeFooterView(View view){
if (mFooterViews.contains(view)){
mFooterViews.remove(view) ;
notifyDataSetChanged();
}
}
}
3>:最后在 MainActivity中调用即可:
/**
* Email: [email protected]
* Created by Novate 2018/5/12 17:06
* Version 1.0
* Params:
* Description:
*/
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView ;
private List mItems;
private WrapRecyclerView recycler_view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wrap_recyclerview) ; // activity_main
mItems = new ArrayList<>();
for (int i=0;i<100;i++){
mItems.add(i);
}
// 使用原始 RecyclerView,每次都必须 创建RecyclerView,然后写装饰设计模式WrapRecyclerAdapter
// 把 RecyclerAdapter 包装到 WrapRecyclerAdapter中,然后setAdapter时候 设置 WrapRecyclerAdapter
// 比较麻烦 ,下边 把RecyclerView封装成WrapRecyclerView
/*
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view) ;
// RecyclerView必须设置布局管理,否则没有数据
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
*//*mRecyclerView.setAdapter(new RecyclerAdapter());*//*
// 采用装饰设计模式,让其支持添加头部和底部
RecyclerAdapter recyclerAdapter = new RecyclerAdapter() ;
WrapRecyclerAdapter wrapRecyclerAdapter = new WrapRecyclerAdapter(recyclerAdapter) ;
mRecyclerView.setAdapter(wrapRecyclerAdapter);
// 添加头部 下边这两种效果是一样的 ,但是这种写法有问题,就是头部只有一小部分,并没有充满屏幕
// 原因就是:父布局容器为null,才导致没有充满屏幕的,只需要添加一个父布局容器,把null修改为mRecyclerView就可以了
// View headView = LayoutInflater.from(this).inflate(R.layout.layout_header_view , null) ;
// View headView = LayoutInflater.from(this).inflate(R.layout.layout_header_view , null , false) ;
View headView = LayoutInflater.from(this).inflate(R.layout.layout_header_view , mRecyclerView , false) ;
wrapRecyclerAdapter.addHeaderView(headView);*/
// 把RecyclerView封装成WrapRecyclerView
recycler_view = (WrapRecyclerView) findViewById(R.id.recycler_view);
recycler_view.setLayoutManager(new LinearLayoutManager(this));
recycler_view.setAdapter(new RecyclerAdapter());
View headView = LayoutInflater.from(this).inflate(R.layout.layout_header_view , recycler_view , false) ;
recycler_view.addHeaderView(headView);
}
private class RecyclerAdapter extends RecyclerView.Adapter{
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_rv , parent , false) ;
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
holder.text.setText("position = "+mItems.get(position));
// 这里是删除RecyclerView 的 item,在这里移除当前item,调用notifyDataSetChanged
// 然后在WrapRecyclerAdapter的构造方法中 注册观察者registerAdapterDataObserver,
// 再去调用调用notifyDataSetChanged就可以删除item了
holder.text.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mItems.remove(position) ;
notifyDataSetChanged();
}
});
}
@Override
public int getItemCount() {
return mItems.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
public TextView text ;
public ViewHolder(View itemView) {
super(itemView);
text = (TextView) itemView.findViewById(R.id.text) ;
}
}
}
}