随时随地为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); } } }
布局文件:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?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>
效果如下:
大家都知道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对象 */ 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; //删除适配器中的尾部 }