RecyclerView添加Header的正确方式

如何为RecyclerView添加Header

大家在使用ListView的时候可以很轻松的添加headers, 但是不知道大家发现没有,RecyclerView和各种LayoutManager都没有哪个方法是为添加header而设立的,这个时候我们就开始思考如何为RecyclerView添加header了。 这里我们的解决方案和网上你能搜到的大多数方案一样,是通过控制Adapter的itemType来设置的,思路就是根据不同的itemType去加载不同的布局。

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    public static final int TYPE_HEADER = 0;
    public static final int TYPE_NORMAL = 1;

    private ArrayList<String> mDatas = new ArrayList<>();

    private View mHeaderView;

    private OnItemClickListener mListener;

    public void setOnItemClickListener(OnItemClickListener li) {
        mListener = li;
    }

    public void setHeaderView(View headerView) {
        mHeaderView = headerView;
        notifyItemInserted(0);
    }

    public View getHeaderView() {
        return mHeaderView;
    }

    public void addDatas(ArrayList<String> datas) {
        mDatas.addAll(datas);
        notifyDataSetChanged();
    }

    @Override
    public int getItemViewType(int position) {
        if(mHeaderView == null) return TYPE_NORMAL;
        if(position == 0) return TYPE_HEADER;
        return TYPE_NORMAL;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if(mHeaderView != null && viewType == TYPE_HEADER) return new Holder(mHeaderView);
        View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
        return new Holder(layout);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        if(getItemViewType(position) == TYPE_HEADER) return;

        final int pos = getRealPosition(viewHolder);
        final String data = mDatas.get(pos);
        if(viewHolder instanceof Holder) {
            ((Holder) viewHolder).text.setText(data);
            if(mListener == null) return;
            viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mListener.onItemClick(pos, data);
                }
            });
        }
    }

    public int getRealPosition(RecyclerView.ViewHolder holder) {
        int position = holder.getLayoutPosition();
        return mHeaderView == null ? position : position - 1;
    }

    @Override
    public int getItemCount() {
        return mHeaderView == null ? mDatas.size() : mDatas.size() + 1;
    }

    class Holder extends RecyclerView.ViewHolder {

        TextView text;

        public Holder(View itemView) {
            super(itemView);
            if(itemView == mHeaderView) return;
            text = (TextView) itemView.findViewById(R.id.text);
        }
    }

    interface OnItemClickListener {
        void onItemClick(int position, String data);
    }
}

这里我们重写了getItemViewType方法,并根据位置来返回不同的type,这个type是我们预先商定好的常量,接在onCreateViewHolder方法中来判断itemType,如果是header,则返回我们设置的headerView,否则正常加载item布局,相信大家对于上面的代码不会有任何疑问,接下来我们就在Activity中用一下试试看,

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mRecyclerView = (RecyclerView) findViewById(R.id.list);
    mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
    mRecyclerView.setLayoutManager(mLayoutManager);
    mRecyclerView.setItemAnimator(new DefaultItemAnimator());

    mAdapter = new MyAdapter();
    mRecyclerView.setAdapter(mAdapter);
    mAdapter.addDatas(generateData());
    setHeader(mRecyclerView);
    mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(int position, String data) {
            Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
        }
    });
}

private void setHeader(RecyclerView view) {
    View header = LayoutInflater.from(this).inflate(R.layout.header, view, false);
    mAdapter.setHeaderView(header);
}

这里LayoutManager我们使用了LinearLayoutManager,并且给Adapter设置了一个header,运行一下
看看效果:

这里写图片描述

恩,还不错,item的点击事件也很完美,那接下来,我们将LayoutManager换成GridLayoutManager看看咋样。

为GridLayoutManager添加header

//  mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mLayoutManager = new GridLayoutManager(this, 2);

这里写图片描述

哎哟,我的小心脏啊,快受不了了,这是什么玩意,我们的header竟然作为一个cell出现在了界面上,这完全不是我们想要的效果啊! 冷静下来想想,肯定会有解决方法的吧。这时候我们就该引入一个不太常用的方法了:

gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
        return getItemViewType(position) == TYPE_HEADER
                ? gridManager.getSpanCount() : 1;
    }
});

我们解释一下这段代码,首先我们设置了一个SpanSizeLookup,这个类是一个抽象类,而且仅有一个抽象方法getSpanSize,这个方法的返回值决定了我们每个position上的item占据的单元格个数,而我们这段代码综合上面为GridLayoutManager设置的每行的个数来解释的话,
就是当前位置是header的位置,那么该item占据2个单元格,正常情况下占据1个单元格。那这段代码放哪呢? 为了以后的封装,我们还是在Adapter中找方法放吧。
我们在Adapter中再重写一个方法onAttachedToRecyclerView,

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
    RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
    if(manager instanceof GridLayoutManager) {
        final GridLayoutManager gridManager = ((GridLayoutManager) manager);
        gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return getItemViewType(position) == TYPE_HEADER
                        ? gridManager.getSpanCount() : 1;
            }
        });
    }
}

这个方法在RecyclerView.setAdapter(MyAdapter)时就会自动执行;

这个时候我们再来看一下效果,

这里写图片描述

恩,这次达到我们的要求了,不过对于StaggeredGridLayoutManager我们还没做处理,而且我们还发现StaggeredGridLayoutManager中并没有像GridLayoutManager中这样的方法,我们还需要单独为StaggeredGridLayoutManager单独处理一下。

为StaggeredGridLayoutManager添加header

我们继续重写Adapter中另外一个方法。

@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
    super.onViewAttachedToWindow(holder);
    ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
    if(lp != null
            && lp instanceof StaggeredGridLayoutManager.LayoutParams
            && holder.getLayoutPosition() == 0) {
        StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
        p.setFullSpan(true);
    }
}

这里的处理方式是用通过LayoutParams,而且这里更简单,StaggeredGridLayoutManager.LayoutParams为我们提供了一个setFullSpan方法来设置占领全部空间,好开心,看一下StaggeredGridLayoutManager的效果,

模拟瀑布流方法:

在adapter中的bindViewHolder()方法中,设置layoutparams.height为随机值

 @Override
        public void bindViewHolder(RecyclerView.ViewHolder parent, String s, int position) {
            if(parent instanceof MyHolder){
                ((MyHolder) parent).mTextView.setText(s);
            }

            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) parent.itemView.getLayoutParams();
            for(int i=0;i<100;i++){
                mheight.add((int)(100+Math.random()*300));
            }
            params.height = mheight.get(position);
            parent.itemView.setLayoutParams(params);
        }

我们需要知道的是:在RecyclerView中,控制展示方式的是LayoutManager。因此,我们为ItemView设置LayoutParams就可以改变recyclerView中每一项的布局。

这里写图片描述

处理分隔符

这是我们开开心心的继续写代码,并且为我们的item添加了分隔符,分隔符我还是用的翔哥写的那个,毕竟翔哥写的太好了,而且我们没有必要重复造轮子,不过这时候问题出现了,相信你也肯定能猜到应该会出现问题了,因为不管我们怎么处理,header对于RecyclerView来说还是一个普普通通的item,这时候我们添加分割线,肯定也会对header产生影响,那下面,我们再来对翔哥的分割线改造一下吧。

public class GridItemDecoration extends RecyclerView.ItemDecoration {
    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
    private Drawable mDivider;
    private boolean hasHeader;

    public GridItemDecoration(Context context) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
    }

    public GridItemDecoration(Context context, boolean header) {
        this(context);
        hasHeader = header;
    }

    ...

    @Override
    public void getItemOffsets(Rect outRect, View view,
                               RecyclerView parent, RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(view);
        int spanCount = getSpanCount(parent);
        int childCount = parent.getAdapter().getItemCount();
        int pos = position;

        if(hasHeader) {
            if(position == 0) {
                outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
                return;
            } else {
                pos = position - 1;
            }
        }

        if (isLastColum(parent, pos, spanCount, childCount)) {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(),
                    mDivider.getIntrinsicHeight());
        }
    }
}

改造的地方是获取偏移量的方法我们换了一个,因为原来的那个已经过时了,而且,这里我们还加了一个boolean类型的hasHeader变量来表示是不是有header,如果hasHeader并且position为0,那么我们仅仅绘制底部的分割线,其他的地方不绘制,在有header的情况下,我们还需要将position减1,因为我们认为的第1个item其实是第2个。这个时候我们再来看看有分割线的效果。

这里写图片描述

看来我们的想法是对的,header部分除了底部有一个分割线外,并没有其他的分割线,这也完全符合我们的需求。

封装

这下好了,基本上完美的处理好了,可是难道我们对于不同的Adapter都需要写那么多代码吗? 对于一个懒程序员来说,这肯定是一个可怕的事情,所以,我们还需要对我们的Adapter进行封装,目的就是可以轻轻松松的写代码,

/**
 * Created by Administration on 2017/3/30.
 */
public abstract class BaseRecycylerAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int TYPE_HEADER = 0;//有hader的状态
    private static final int TYPE_NORMAL = 1;//没有header的状态
    private static final int TYPE_FOOTER = 2;//有footor的状态


    private ArrayList<T> mDatas = new ArrayList<>();

    private View headView;//头布局
    private View footView;//底布局
    private boolean hasHeadView;//是否有头布局
    private boolean hasFootView;//是否有底布局

    private OnItemClickListener mListener;

    public void setOnItemClickListener(OnItemClickListener li) {
        mListener = li;
    }

    public void addHeadView(View headView){
        if(headView!=null){
            this.headView = headView;
            this.hasHeadView=true;
            notifyDataSetChanged();
        }

    }



    private void removeHeaderView(){
        if(headView!=null){
            notifyItemRemoved(0);
            this.headView = null;
            this.hasHeadView = false;
            notifyDataSetChanged();
        }
    }

    public View getHeadView() {
        return headView;
    }

    private void addFootView(View footView){
        if(footView!=null){
            this.footView = footView;
            this.hasFootView = true;
            notifyDataSetChanged();
        }
    }

    private void removeFooterView(){
        if(footView!=null){
            notifyItemRemoved(getItemCount()-1);
            this.footView = null;
            this.hasFootView = false;
            notifyDataSetChanged();
        }
    }

    public View getFootView() {
        return footView;
    }

    public void addData(ArrayList<T> datas){
        mDatas.addAll(datas);
        notifyDataSetChanged();
    }

    @Override
    public int getItemViewType(int position) {
        if(checkIsHeaderView(position)){
            return TYPE_HEADER;
        }else if(checkIsFooterView(position)){
            return TYPE_FOOTER;
        }else{
            return  TYPE_NORMAL;
        }
    }

    private boolean checkIsFooterView(int position) {
        return hasFootView && position == getItemCount()-1;
    }

    private boolean checkIsHeaderView(int position) {
        return hasHeadView && position == 0;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if(hasHeadView&&viewType==TYPE_HEADER){
            return new HeaderViewHolder(headView);
        }
        if(hasFootView&&viewType==TYPE_FOOTER){
            return new FooterViewHolder(footView);
        }
        return onCreate(parent,viewType);
    }


    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
            if(checkIsHeaderView(position)&&getItemViewType(position)==TYPE_HEADER){
                return ;
            }
            if(checkIsFooterView(position)&&getItemViewType(position)==TYPE_FOOTER){
                return;
            }
            int realPosition = getRealPosition(holder);
            final T data = mDatas.get(realPosition);
            bindViewHolder(holder,data,realPosition);

        if(mListener!=null){
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    mListener.onItemClick(position,data,holder);
                }
            });
        }

    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);

        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if(manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    return getItemViewType(position) == TYPE_HEADER
                            ? gridManager.getSpanCount() : 1;
                }
            });
        }
    }

    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        if(lp != null
                && lp instanceof StaggeredGridLayoutManager.LayoutParams
                && holder.getLayoutPosition() == 0) {
            StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
            p.setFullSpan(true);
        }
    }

    private int getRealPosition(RecyclerView.ViewHolder holder) {
        int position = holder.getAdapterPosition();//position : 1

        if(hasHeadView||hasFootView){
            return position-1;
        }
        else if(hasHeadView&hasFootView){
            return position-2;
        }
        else{
            return position;
        }

    }

    @Override
    public int getItemCount() {
        return getItems() + (hasHeadView ? 1 : 0) + (hasFootView ? 1 : 0);
    }

    private class HeaderViewHolder extends RecyclerView.ViewHolder {
        public HeaderViewHolder(View  itemView) {
            super(itemView);
            itemView.setOnClickListener(null);
        }
    }

    private class FooterViewHolder extends RecyclerView.ViewHolder {
        public FooterViewHolder(View footView) {
            super(footView);
            footView.setOnClickListener(null);
        }
    }

    public abstract int getItems();
    public abstract RecyclerView.ViewHolder onCreate(ViewGroup parent,int viewType);
    public abstract void bindViewHolder(RecyclerView.ViewHolder parent,T t,int position);

    public interface OnItemClickListener<T> {
        void onItemClick(int position, T data, RecyclerView.ViewHolder holder);
    }
}

我们将BaseRecyclerAdapter抽象起来,并且提供两个抽象方法onCreate和onBind用来创建holder和绑定数据,而对于header做的一系列工作,我们都放到了BaseRecyclerAdapter中,而继承BaseRecyclerAdapter后,我们仅仅关心我们的holder怎么创建和数据怎么绑定就ok。例如下面代码:

 private class MyAdapter extends BaseRecycylerAdapter<String>{
        private List<Integer> mheight = new ArrayList<>();

        @Override
        public int getItems() {
            return list.size();
        }

        @Override
        public RecyclerView.ViewHolder onCreate(ViewGroup parent, int viewType) {
            LayoutInflater mInflater = LayoutInflater.from(parent.getContext());
            View view = mInflater.inflate(R.layout.item,parent,false);
            return new MyHolder(view);
        }

        @Override
        public void bindViewHolder(RecyclerView.ViewHolder parent, String s, int position) {
            if(parent instanceof MyHolder){
                ((MyHolder) parent).mTextView.setText(s);
            }

            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) parent.itemView.getLayoutParams();
            for(int i=0;i<100;i++){
                mheight.add((int)(100+Math.random()*300));
            }
            params.height = mheight.get(position);
            parent.itemView.setLayoutParams(params);
        }

        private class MyHolder extends RecyclerView.ViewHolder {
            private TextView mTextView;
            public MyHolder(View view) {
                super(view);
                mTextView = (TextView) view.findViewById(R.id.text);
            }
        }
    }

MainActivity.java :

public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerView;
    private MyAdapter mAdapter;

    private ArrayList<String> list = new ArrayList<>();
    private RecyclerView.LayoutManager mLayoutManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initData();

        mAdapter = new MyAdapter();
        mAdapter.addData(list);
        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        mLayoutManager = getLayoutManager();
        recyclerView.setLayoutManager(mLayoutManager);
        recyclerView.setAdapter(mAdapter);
        setHeader(recyclerView);

        mAdapter.setOnItemClickListener(new BaseRecycylerAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(int position, Object data, RecyclerView.ViewHolder holder) {
                if(position==mAdapter.getItems()){
                    Toast.makeText(MainActivity.this, "last one",Toast.LENGTH_SHORT).show();
                }
                else if(position==0){
                    Toast.makeText(MainActivity.this, "first one",Toast.LENGTH_SHORT).show();
                }
                else{
                    Toast.makeText(MainActivity.this, (String) data,Toast.LENGTH_SHORT).show();
                }

            }
        });

    }

    private void setHeader(RecyclerView recyclerView) {
        View header = LayoutInflater.from(this).inflate(R.layout.head_layout, recyclerView, false);

//给头布局设置点击事件
        header.findViewById(R.id.image).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "header click",Toast.LENGTH_SHORT).show();
            }
        });
        mAdapter.addHeadView(header);
    }

    private RecyclerView.LayoutManager getLayoutManager() {
 //       LinearLayoutManager manager = new LinearLayoutManager(this);
 //       GridLayoutManager manager = new GridLayoutManager(this,4);
        StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);
        return manager;
    }

    private void initData() {
        for(int i=0;i<100;i++){
            list.add("#name "+i);
        }
    }

    private class MyAdapter extends BaseRecycylerAdapter<String>{
        private List<Integer> mheight = new ArrayList<>();

        @Override
        public int getItems() {
            return list.size();
        }

        @Override
        public RecyclerView.ViewHolder onCreate(ViewGroup parent, int viewType) {
            LayoutInflater mInflater = LayoutInflater.from(parent.getContext());
            View view = mInflater.inflate(R.layout.item,parent,false);
            return new MyHolder(view);
        }

        @Override
        public void bindViewHolder(RecyclerView.ViewHolder parent, String s, int position) {
            if(parent instanceof MyHolder){
                ((MyHolder) parent).mTextView.setText(s);
            }

            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) parent.itemView.getLayoutParams();
            for(int i=0;i<100;i++){
                mheight.add((int)(100+Math.random()*300));
            }
            params.height = mheight.get(position);
            parent.itemView.setLayoutParams(params);
        }

        private class MyHolder extends RecyclerView.ViewHolder {
            private TextView mTextView;
            public MyHolder(View view) {
                super(view);
                mTextView = (TextView) view.findViewById(R.id.text);
            }
        }
    }
}

这样我们再用起来就简单多了,对于这样的封装,我们还算满意,再做完添加header后,相信大家对于footer也有想法了,有想法就实现它吧,扩展一下BaseRecyclerAdapter就ok啦。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以在 RecyclerView添加头部和尾部视图来实现添加 header 和 footer 的效果。下面是一种常见的实现方式: 首先,你需要创建两个布局文件用作 header 和 footer 的视图。例如,header_view.xml 和 footer_view.xml。 然后,在你的 RecyclerView 的适配器中,你需要创建两个常量来表示 header 和 footer 的视图类型。例如,HEADER_VIEW_TYPE 和 FOOTER_VIEW_TYPE。 接下来,在适配器中,你需要重写以下几个方法: 1. getItemViewType(int position) 方法:根据 position 来返回相应的视图类型。如果 position 是 0,则返回 HEADER_VIEW_TYPE;如果 position 是数据集合的大小加上 1,则返回 FOOTER_VIEW_TYPE;否则返回普通的 item 类型。 2. onCreateViewHolder(ViewGroup parent, int viewType) 方法:根据 viewType 来创建对应的 ViewHolder。如果 viewType 是 HEADER_VIEW_TYPE 或 FOOTER_VIEW_TYPE,则使用相应的布局文件创建 ViewHolder;否则使用普通的 item 布局文件创建 ViewHolder。 3. onBindViewHolder(ViewHolder holder, int position) 方法:根据 position 来绑定数据到 ViewHolder。如果 position 是 HEADER_VIEW_TYPE 或 FOOTER_VIEW_TYPE,则不需要绑定数据;否则绑定普通的 item 数据。 最后,在你的 RecyclerView 中设置适配器,并在数据集合中添加对应的数据项作为 header 和 footer。例如,使用以下代码: ``` MyAdapter adapter = new MyAdapter(dataList); adapter.addHeader(headerData); adapter.addFooter(footerData); recyclerView.setAdapter(adapter); ``` 请注意,上述代码中的 MyAdapter 是你自定义的 RecyclerView.Adapter 子类,其中包含了添加 header 和 footer 的方法。 以上就是在 RecyclerView添加 header 和 footer 的基本步骤。希望能对你有所帮助!如有需要,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值