随时随地为RecyclerView添加头或者尾

参考于:http://blog.csdn.net/lmj623565791/article/details/51854533

注意:

  1.主体内容的单击事件已经设置,可以直接使用,但是头和尾的单击事件需要自己再次单独在外部设置

  2.完全没有修改源代码,所以可以完美与其它控件兼容

  3.没有限制头和尾必须在设置适配器之前添加, 随便啥时候添加都可以

思路:把头和尾都当做一个itemView,也差不多就是多布局吧,只不过让它显示在RecyclerView的头部或者尾部

主要部分也就是适配器的定义了,代码如下:

MyRecyclerViewAdapter.java:            作为ListView使用

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

/**
 * 为RecyclerView添加头和尾
 */
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private Context context;

    /**
     * 记录当前在对应的集合中需要显示的索引
     */
    private int currentPosition;

    /**
     * 作为头的类型值
     */
    private final int headType = 0;

    /**
     * 正常显示内容的类型
     */
    private final int normalType = 1;

    /**
     * 尾部的类型
     */
    private final int footerType = 2;


    /**
     * 显示的内容数据
     */
    private List<String> list;

    /**
     * 存放头的集合
     * 添加头不过就是不同的类型放到最前面的item而已
     */
    private List<View> headList = new ArrayList<>();

    /**
     * 存放尾视图的集合
     */
    private List<View> footerList = new ArrayList<>();

    private OnRecyclerItemClickListener mOnItemClickListener;//单击事件

    public MyRecyclerViewAdapter(Context context, List<String> list) {
        this.context = context;
        this.list = list;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = null;
        switch (viewType){
            case headType: //显示头
                itemView = headList.get(currentPosition);
                return  new HeaderAndFooterHolder(itemView);
            case footerType: //显示尾
                itemView = footerList.get(currentPosition);
                return new HeaderAndFooterHolder(itemView);
            case normalType: //显示中间的主体内容
                //找到item的布局
                itemView= LayoutInflater.from(context).inflate(R.layout.recycler_item_layout,parent,false);
                break;
        }
        return new MyViewHolder(itemView);//将布局设置给holder
    }

    /**
     * 绑定视图到holder,就如同ListView的getView(),但是这里已经把复用实现了,我们只需要填充数据就行,
     * 复用的时候都是调用该方法填充数据. 但是这里需要多布局,所以判断类型,创建不一样的viewHolder对象就行
     */
    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
        int itemViewType = getItemViewType(position);
        //头和尾不需要在适配器中绑定数据,都是在外面填充数据的,所以直接return
        if(itemViewType == headType){
            return;
        }else if(itemViewType == footerType){
            return;
        }
        //为主体内容填充数据,并设置单击事件
        //先强转为对应布局的viewHolder,多布局都是这样的,为不同布局创建不同的ViewHolder就是
        final MyViewHolder myViewHolder = (MyViewHolder) holder;
        //填充数据,切记需要减去头的个数,单击事件也许有
        myViewHolder.textView.setText(list.get(position - headList.size())+"");
        //为主体内容设置单击事件,没有头和尾的单击事件
        if(mOnItemClickListener !=null){
            myViewHolder.textView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //这里是为textView设置了单击事件,回调出去,传回点击的view和在适配器中的位置
                    //mOnItemClickListener.onItemClick(v,position);这里需要获取布局中的position,不然乱序
                    mOnItemClickListener.onItemClick(v,myViewHolder.getLayoutPosition()- headList.size());
                }
            });
        }

    }

    /**
     * 根据当前需要显示的position来判断显示的具体类型,这也是实现多布局的关键
     */
    @Override
    public int getItemViewType(int position) {
        //需要记录显示的position值,方便onCreateViewHolder()方法的使用
        if(isHeader(position)){
            currentPosition = position;//头就直接相等
            return headType;
        }else if(isFooter(position)){
            currentPosition = position - headList.size() - list.size();//记录显示尾部集合中的哪个索引
            return footerType;
        }
        currentPosition = position - headList.size();//记录显示中间内容的索引
        return normalType;
    }

    @Override
    public int getItemCount() {
        //RecyclerView的个数就变为头,尾,主体内容的总个数了
        return list.size() + headList.size() + footerList.size();
    }


    class MyViewHolder extends RecyclerView.ViewHolder{

        TextView textView;

        public MyViewHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView.findViewById(R.id.textView);
        }
    }

    /**
     * 作为头和尾的ViewHolder
     */
    class HeaderAndFooterHolder extends RecyclerView.ViewHolder{

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

    /**
     * 根据索引,判断当前需要显示布局是否是添加的头
     */
    private boolean isHeader(int position){
        return position < headList.size();
    }

    /**
     * 根据索引,判断当前需要显示布局是否是添加的尾
     */
    private boolean isFooter(int position){
        return position >= headList.size() + list.size();
    }

    /**
     * 为RecyclerView添加头
     * 对于事件应该是在addHeaderView等方法前设置。即不在适配器中设置头和尾的单击事件
     */
    public void addHeaderView(View view){
        headList.add(view);
        notifyItemInserted(headList.size() -1);//通知更新
    }

    /**
     * 为RecyclerView添加尾
     */
    public void addFooterView(View view){
        footerList.add(view);
        notifyItemInserted(getItemCount() - 1);//通知插入了itemView
    }

    /**
     * 处理item的点击事件,因为recycler没有提供单击事件,所以只能自己写了
     */
    interface OnRecyclerItemClickListener {
        void onItemClick(View view, int position);//点击的View,适配器中的索引position
    }


    /**
     * 暴露给外面的设置单击事件
     */
    public void setOnItemClickListener(OnRecyclerItemClickListener onItemClickListener){
        mOnItemClickListener = onItemClickListener;
    }

}

MainActivity.java:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    /**
     * 显示的数据
     */
    private ArrayList<String> mDatas;

    /**
     * RecyclerView的适配器
     */
    private MyRecyclerViewAdapter adapter;


    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        //初始化RecyclerView的设置
        settingRecyclerView();

        //按钮,添加头的单击事件
        findViewById(R.id.addHeader).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TextView textView = new TextView(MainActivity.this);
                textView.setText("===我是添加的头");
                adapter.addHeaderView(textView);
            }
        });

        //按钮,添加尾的单击事件
        findViewById(R.id.addFooter).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TextView textView = new TextView(MainActivity.this);
                textView.setText("我是添加的尾====");
                adapter.addFooterView(textView);
            }
        });
    }

    /**
     * recyclerView的初始化和设置
     */
    private void settingRecyclerView() {
        //2.创建布局管理器,LinearLayoutManager代表线性的,也就有垂直和水平方向了
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        //3.给linearLayoutManager设置滑动的方向
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        //4.为recyclerView设置布局管理器
        recyclerView.setLayoutManager(linearLayoutManager);

        initData();//初始化数据
        //3.创建适配器
        adapter = new MyRecyclerViewAdapter(this, mDatas);
        //设置添加,移除item的动画,DefaultItemAnimator为默认的
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        //4.设置适配器
        recyclerView.setAdapter(adapter);

        //item的点击事件,在适配器的单击回调中我已经减去了头的个数,所以这里可以直接使用
        adapter.setOnItemClickListener(new MyRecyclerViewAdapter.OnRecyclerItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(MainActivity.this,"单击了:"+mDatas.get(position), Toast.LENGTH_SHORT).show();
                Log.i("tag", "点击的: "+position);
            }
        });

    }

    //初始化数据
    protected void initData(){
        mDatas = new ArrayList<String>();
        for (int i = 'A'; i < 'z'; i++){
            mDatas.add("" + (char) i);
        }
    }

}

布局文件:

<?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">

    <Button
        android:text="添加头"
        android:id="@+id/addHeader"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:text="添加尾"
        android:id="@+id/addFooter"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>

</LinearLayout>
View Code

效果如下:

大家都知道RecyclerView比较强大,可以设置不同的LayoutManager,那么我们换成GridLayoutMananger再看看效果。

好像发现了不对的地方,我们的headerView果真被当成普通的Item处理了,不过由于我们的编写方式,出现上述情况是可以理解的。

那么我们该如何处理呢?让每个headerView独立的占据一行?

2、进一步的完善 使用GridLayoutMananger时的bug

 在上面的适配器中重写下面的方法:

/**
     * 针对GridLayoutManager,解决添加的头不会单独占据一行
     * @param recyclerView
     */
    @Override
    public void onAttachedToRecyclerView(RecyclerView 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)
                {
                    int viewType = getItemViewType(position);
                    if (viewType == headType) {
                        return gridLayoutManager.getSpanCount();//跨距整个列数
                    } else if (viewType == footerType) {
                        return gridLayoutManager.getSpanCount();
                    }
                    return 1;//正常情况为1列
                }
            });
            gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
        }
    }

效果如下:

3、进一步的完善 使用StaggeredGridLayoutManager作为瀑布流显示时的bug

如果直接运行效果如下:

对于StaggeredGridLayoutManager瀑布流的效果,需要重写onViewAttachedToWindow方法,如下:

/**
     * 解决添加头对瀑布流的问题
     */
    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        int layoutPosition = holder.getLayoutPosition();
        //根据布局索引,判断是否是头,或者尾
        if(isHeader(layoutPosition) || isFooter(layoutPosition)){
            ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams){
                StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
                p.setFullSpan(true);//跨越整个列数
            }
        }
    }

效果如下:  由于我这里没有设置随机高度,所以高度一致,毕竟这不是重点,如果想看瀑布流实现的具体方式,可以参考上一篇文章:

RecyclerView实现的瀑布流效果

 滑动监听:

/**
     * 获取RecyclerView对象
     */
    private void getRecyclerView() {
        int childs = getChildCount();
        if (childs > 0) {
            View childView = getChildAt(0);
            if (childView instanceof RecyclerView) {
                mRecyclerView = (RecyclerView) childView;
                LayoutManager layoutManager = mRecyclerView.getLayoutManager();
                if(layoutManager instanceof LinearLayoutManager){
                    final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
                    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

                        @Override  
                        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {  
                            super.onScrollStateChanged(recyclerView, newState);  
                            if (totalItemCount == lastVisibleItem && newState == RecyclerView.SCROLL_STATE_IDLE) {
                                if (!isLoading) {
                                    isLoading = true;
                                    //添加适配器中的尾部
                                    mOnLoadListener.onLoad();
                                }
                            }
                        }  
                        
                        @Override  
                        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {  
                            super.onScrolled(recyclerView, dx, dy);  
                            totalItemCount = linearLayoutManager.getItemCount(); 
                            lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition() + 1;
                        }  
                    });
                    
                }
                Log.d("tag", "找到了RecyclerView");
            }
        }else{
            Toast.makeText(getContext(), "SwipeRefreshAndLoadLayout的子控件只能是RecyclerView", 0).show();
        }
    }

    /**
     * 设置上拉加载更多的回调
     * @param mOnLoadListener
     */
    public void setInterface(OnLoadListener mOnLoadListener) {
        this.mOnLoadListener = mOnLoadListener;
    }

    /**
     * 加载更多的监听器
     */
    public static interface OnLoadListener {
        public void onLoad();
    }
    
    /**
     * 加载完毕
     */
    public void loadComplete() {
        isLoading = false;
        //删除适配器中的尾部
    }

 

posted @ 2016-10-22 17:30  ts-android  阅读(637)  评论(0编辑  收藏  举报