RecyclerView使用大全
RecylerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字recylerview即回收view也可以看出。官方对于它的介绍则是:RecyclerView 是 ListView 的升级版本,更加先进和灵活。RecyclerView通过设置LayoutManager,ItemDecoration,ItemAnimator实现你想要的效果。
使用LayoutManager来确定每一个item的排列方式。
使用ItemDecoration自己绘制分割线,更灵活
使用ItemAnimator为增加或删除一行设置动画效果。
注意新建完项目,需要在app/build.gradle增加RecylerView依赖,不然找不到RecyclerView类
compile'com.android.support:recyclerview-v7:23.1.0'RecylerView简单的Demo
我们来看activity代码,跟ListView写法差不多,只是这边多设置了布局管理器。
publicclassLinearLayoutActivityextendsAppCompatActivity{privateRecyclerViewrecyclerView;privateRecyclerViewAdapteradapter;privateList<String>datas;@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.recycler_main);initData();recyclerView=(RecyclerView)findViewById(R.id.recyclerview);recyclerView.setLayoutManager(newLinearLayoutManager(this));//设置布局管理器recyclerView.addItemDecoration(newDividerItemDecoration(this));recyclerView.setAdapter(adapter=newRecyclerViewAdapter(this,datas));}privatevoidinitData(){datas=newArrayList<>();for(inti=0;i<100;i++){datas.add("item:"+i);}}}
activity对应的布局文件:recycler_main.xml
<?xmlversion="1.0"encoding="utf-8"?><RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><android.support.v7.widget.RecyclerViewandroid:id="@+id/recyclerview"android:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout>
adapter相对ListView来说变化比较大的。把ViewHolder逻辑封装起来了,代码相对简单一些。
需要继承RecyclerView.Adapter,重写三个方法
MyViewHolder需要继承RecyclerView.ViewHolder
publicclassRecyclerViewAdapterextendsRecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder>{privateList<String>datas;privateLayoutInflaterinflater;publicRecyclerViewAdapter(Contextcontext,List<String>datas){inflater=LayoutInflater.from(context);this.datas=datas;}//创建每一行的View用RecyclerView.ViewHolder包装@OverridepublicRecyclerViewAdapter.MyViewHolderonCreateViewHolder(ViewGroupparent,intviewType){ViewitemView=inflater.inflate(R.layout.recycler_item,null);returnnewMyViewHolder(itemView);}//给每一行View填充数据@OverridepublicvoidonBindViewHolder(RecyclerViewAdapter.MyViewHolderholder,intposition){holder.textview.setText(datas.get(position));}//数据源的数量@OverridepublicintgetItemCount(){returndatas.size();}classMyViewHolderextendsRecyclerView.ViewHolder{privateTextViewtextview;publicMyViewHolder(ViewitemView){super(itemView);textview=(TextView)itemView.findViewById(R.id.textview);}}}
我们来看看效果图:
RecyclerView增加分隔线RecyclerView是没有android:divider跟android:dividerHeight属性的,如果我们需要分割线,就只能自己动手去实现了。
需要继承ItemDecoration类,实现onDraw跟getItemOffsets方法。
调用RecyclerView的addItemDecoration方法。
我们先写一个DividerItemDecoration类,继承RecyclerView.ItemDecoration,在getItemOffsets留出item之间的间隔,然后就会调用onDraw方法绘制(onDraw的绘制优先于每一行的绘制)
publicclassDividerItemDecorationextendsRecyclerView.ItemDecoration{/**RecyclerView的布局方向,默认先赋值为纵向布局*RecyclerView布局可横向,也可纵向*横向和纵向对应的分割线画法不一样**/privateintmOrientation=LinearLayoutManager.VERTICAL;privateintmItemSize=1;//item之间分割线的size,默认为1privatePaintmPaint;//绘制item分割线的画笔,和设置其属性publicDividerItemDecoration(Contextcontext){this(context,LinearLayoutManager.VERTICAL,R.color.colorAccent);}publicDividerItemDecoration(Contextcontext,intorientation){this(context,orientation,R.color.colorAccent);}publicDividerItemDecoration(Contextcontext,intorientation,intdividerColor){this(context,orientation,dividerColor,1);}/***@paramcontext*@paramorientation绘制方向*@paramdividerColor分割线颜色颜色资源id*@parammItemSize分割线宽度传入dp值就行*/publicDividerItemDecoration(Contextcontext,intorientation,intdividerColor,intmItemSize){this.mOrientation=orientation;if(orientation!=LinearLayoutManager.VERTICAL&&orientation!=LinearLayoutManager.HORIZONTAL){thrownewIllegalArgumentException("请传入正确的参数");}//把dp值换算成pxthis.mItemSize=(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,mItemSize,context.getResources().getDisplayMetrics());mPaint=newPaint(Paint.ANTI_ALIAS_FLAG);mPaint.setColor(context.getResources().getColor(dividerColor));}@OverridepublicvoidonDraw(Canvasc,RecyclerViewparent,RecyclerView.Statestate){if(mOrientation==LinearLayoutManager.VERTICAL){drawVertical(c,parent);}else{drawHorizontal(c,parent);}}/***绘制纵向item分割线*@paramcanvas*@paramparent*/privatevoiddrawVertical(Canvascanvas,RecyclerViewparent){finalintleft=parent.getPaddingLeft();finalintright=parent.getMeasuredWidth()-parent.getPaddingRight();finalintchildSize=parent.getChildCount();for(inti=0;i<childSize;i++){finalViewchild=parent.getChildAt(i);RecyclerView.LayoutParamslayoutParams=(RecyclerView.LayoutParams)child.getLayoutParams();finalinttop=child.getBottom()+layoutParams.bottomMargin;finalintbottom=top+mItemSize;canvas.drawRect(left,top,right,bottom,mPaint);}}/***绘制横向item分割线*@paramcanvas*@paramparent*/privatevoiddrawHorizontal(Canvascanvas,RecyclerViewparent){finalinttop=parent.getPaddingTop();finalintbottom=parent.getMeasuredHeight()-parent.getPaddingBottom();finalintchildSize=parent.getChildCount();for(inti=0;i<childSize;i++){finalViewchild=parent.getChildAt(i);RecyclerView.LayoutParamslayoutParams=(RecyclerView.LayoutParams)child.getLayoutParams();finalintleft=child.getRight()+layoutParams.rightMargin;finalintright=left+mItemSize;canvas.drawRect(left,top,right,bottom,mPaint);}}/***设置item分割线的size*@paramoutRect*@paramview*@paramparent*@paramstate*/@OverridepublicvoidgetItemOffsets(RectoutRect,Viewview,RecyclerViewparent,RecyclerView.Statestate){if(mOrientation==LinearLayoutManager.VERTICAL){outRect.set(0,0,0,mItemSize);//垂直排列底部偏移}else{outRect.set(0,0,mItemSize,0);//水平排列右边偏移}}}
不要忘记调用addItemDecoration方法哦
recyclerView.addItemDecoration(newDividerItemDecoration(this));//添加分割线
重新运行,效果图:
大家读到这里肯定会有一个疑问,这货比ListView麻烦多了啊,但是google官方为什么要说是ListView的升级版呢?接下来开始放大招。。。
GridLayoutManager在RecyclerView中实现不同的列表,只需要切换不同的LayoutManager即可。RecyclerView.LayoutManager跟RecyclerView.ItemDecoration一样,都是RecyclerView静态抽象内部类,但是LayoutManager有三个官方写好的实现类。
LinearLayoutManager 线性布局管理器 跟ListView功能相似
GridLayoutManager 网格布局管理器 跟GridView功能相似
StaggeredGridLayoutManager 瀑布流布局管理器
刚刚我们用的是LinearLayoutManager,现在我们切换到GridLayoutManager,看到下面这句代码,有没有感觉分分钟切换不同列表显示。
recyclerView.setLayoutManager(newGridLayoutManager(this,2));
如果要显示多列或者要纵向显示就new不同的构造方法,以下代码纵向显示4列。当前如果你还需要反方向显示,把false改成true就可以。
recyclerView.setLayoutManager(newGridLayoutManager(this,4,GridLayoutManager.HORIZONTAL,false));
因为用的是网格布局,所以呢绘制分割线的代码需要重新修改一下。网格布局一行可以有多列,并且最后一列跟最后一行不需要绘制,所以我们得重新创建一个类。
DividerGridItemDecoration.java
publicclassDividerGridItemDecorationextendsRecyclerView.ItemDecoration{/**RecyclerView的布局方向,默认先赋值为纵向布局*RecyclerView布局可横向,也可纵向*横向和纵向对应的分割线画法不一样**/privateintmOrientation=LinearLayoutManager.VERTICAL;privateintmItemSize=1;//item之间分割线的size,默认为1privatePaintmPaint;//绘制item分割线的画笔,和设置其属性publicDividerGridItemDecoration(Contextcontext){this(context,LinearLayoutManager.VERTICAL,R.color.colorAccent);}publicDividerGridItemDecoration(Contextcontext,intorientation){this(context,orientation,R.color.colorAccent);}publicDividerGridItemDecoration(Contextcontext,intorientation,intdividerColor){this(context,orientation,dividerColor,1);}/***@paramcontext*@paramorientation绘制方向*@paramdividerColor分割线颜色颜色资源id*@parammItemSize分割线宽度传入dp值就行*/publicDividerGridItemDecoration(Contextcontext,intorientation,intdividerColor,intmItemSize){this.mOrientation=orientation;if(orientation!=LinearLayoutManager.VERTICAL&&orientation!=LinearLayoutManager.HORIZONTAL){thrownewIllegalArgumentException("请传入正确的参数");}//把dp值换算成pxthis.mItemSize=(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,mItemSize,context.getResources().getDisplayMetrics());mPaint=newPaint(Paint.ANTI_ALIAS_FLAG);mPaint.setColor(context.getResources().getColor(dividerColor));}@OverridepublicvoidonDraw(Canvasc,RecyclerViewparent,RecyclerView.Statestate){drawHorizontal(c,parent);drawVertical(c,parent);}privateintgetSpanCount(RecyclerViewparent){//列数intspanCount=-1;RecyclerView.LayoutManagerlayoutManager=parent.getLayoutManager();if(layoutManagerinstanceofGridLayoutManager){spanCount=((GridLayoutManager)layoutManager).getSpanCount();}elseif(layoutManagerinstanceofStaggeredGridLayoutManager){spanCount=((StaggeredGridLayoutManager)layoutManager).getSpanCount();}returnspanCount;}publicvoiddrawHorizontal(Canvascanvas,RecyclerViewparent){intchildCount=parent.getChildCount();for(inti=0;i<childCount;i++){finalViewchild=parent.getChildAt(i);finalRecyclerView.LayoutParamsparams=(RecyclerView.LayoutParams)child.getLayoutParams();finalintleft=child.getLeft()-params.leftMargin;finalintright=child.getRight()+params.rightMargin+mItemSize;finalinttop=child.getBottom()+params.bottomMargin;finalintbottom=top+mItemSize;canvas.drawRect(left,top,right,bottom,mPaint);}}publicvoiddrawVertical(Canvascanvas,RecyclerViewparent){finalintchildCount=parent.getChildCount();for(inti=0;i<childCount;i++){finalViewchild=parent.getChildAt(i);finalRecyclerView.LayoutParamsparams=(RecyclerView.LayoutParams)child.getLayoutParams();finalinttop=child.getTop()-params.topMargin;finalintbottom=child.getBottom()+params.bottomMargin;finalintleft=child.getRight()+params.rightMargin;finalintright=left+mItemSize;canvas.drawRect(left,top,right,bottom,mPaint);}}@OverridepublicvoidgetItemOffsets(RectoutRect,intitemPosition,RecyclerViewparent){intspanCount=getSpanCount(parent);intchildCount=parent.getAdapter().getItemCount();if(isLastRow(parent,itemPosition,spanCount,childCount)){//如果是最后一行,不需要绘制底部outRect.set(0,0,mItemSize,0);}elseif(isLastColum(parent,itemPosition,spanCount,childCount)){//如果是最后一列,不需要绘制右边outRect.set(0,0,0,mItemSize);}else{outRect.set(0,0,mItemSize,mItemSize);}}privatebooleanisLastColum(RecyclerViewparent,intpos,intspanCount,intchildCount){RecyclerView.LayoutManagerlayoutManager=parent.getLayoutManager();if(layoutManagerinstanceofGridLayoutManager){if((pos+1)%spanCount==0){//如果是最后一列,则不需要绘制右边returntrue;}}elseif(layoutManagerinstanceofStaggeredGridLayoutManager){intorientation=((StaggeredGridLayoutManager)layoutManager).getOrientation();if(orientation==StaggeredGridLayoutManager.VERTICAL){if((pos+1)%spanCount==0){//如果是最后一列,则不需要绘制右边returntrue;}}else{childCount=childCount-childCount%spanCount;if(pos>=childCount)//如果是最后一列,则不需要绘制右边returntrue;}}returnfalse;}privatebooleanisLastRow(RecyclerViewparent,intpos,intspanCount,intchildCount){RecyclerView.LayoutManagerlayoutManager=parent.getLayoutManager();if(layoutManagerinstanceofGridLayoutManager){childCount=childCount-childCount%spanCount;if(pos>=childCount)//最后一行returntrue;}elseif(layoutManagerinstanceofStaggeredGridLayoutManager){intorientation=((StaggeredGridLayoutManager)layoutManager).getOrientation();if(orientation==StaggeredGridLayoutManager.VERTICAL){//纵向childCount=childCount-childCount%spanCount;if(pos>=childCount)//最后一行returntrue;}else{//横向if((pos+1)%spanCount==0){//是最后一行returntrue;}}}returnfalse;}}
写了这两个画分割线的类,主流的布局:线性列表跟网格列表都能展示了。。。赶紧运行代码看看结果:
StaggeredGridLayoutManageractviity中修改下布局管理器,大家应该感觉很熟悉了吧~~~
recyclerView.setLayoutManager(newStaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL));
瀑布流列表一般列的高度是不一致的,为了模拟不同的宽高,数据源我把String类型改成了对象.然后初始化的时候随机了一个高度.
publicclassItemData{privateStringcontent;//item内容privateintheight;//item高度publicItemData(){}publicItemData(Stringcontent,intheight){this.content=content;this.height=height;}publicStringgetContent(){returncontent;}publicvoidsetContent(Stringcontent){this.content=content;}publicintgetHeight(){returnheight;}publicvoidsetHeight(intheight){this.height=height;}}
瀑布流列表没有添加分割线,给item布局设置了android:padding属性。recycler_staggered_item.xml
<?xmlversion="1.0"encoding="utf-8"?><FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:padding="5dp"android:layout_width="wrap_content"android:layout_height="match_parent"><TextViewandroid:id="@+id/textview"android:background="@color/colorAccent"android:layout_width="100dp"android:layout_height="wrap_content"android:gravity="center"android:text="122"android:textSize="20sp"/></FrameLayout>
最后我们在适配器的onBindViewHolder方法中给itemd中的TextView设置一个高度
@OverridepublicvoidonBindViewHolder(StaggeredGridAdapter.MyViewHolderholder,intposition){ItemDataitemData=datas.get(position);holder.textview.setText(itemData.getContent());//手动更改高度,不同位置的高度有所不同holder.textview.setHeight(itemData.getHeight());}
是不是感觉so easy,赶紧运行看看效果:
添加header跟footerRecyclerView添加头部跟底部是没有对应的api的,但是我们很多的需求都会用到,于是只能自己想办法实现了。我们可以通过适配器的getItemViewType方法来实现这个功能。
修改后的适配器代码:RecyclerHeadFootViewAdapter.java
publicclassRecyclerHeadFootViewAdapterextendsRecyclerView.Adapter<RecyclerView.ViewHolder>{privateList<String>datas;privateLayoutInflaterinflater;publicstaticfinalintTYPE_HEADER=1;//header类型publicstaticfinalintTYPE_FOOTER=2;//footer类型privateViewheader=null;//头ViewprivateViewfooter=null;//脚ViewpublicRecyclerHeadFootViewAdapter(Contextcontext,List<String>datas){inflater=LayoutInflater.from(context);this.datas=datas;}//创建每一行的View用RecyclerView.ViewHolder包装@OverridepublicRecyclerView.ViewHolderonCreateViewHolder(ViewGroupparent,intviewType){if(viewType==TYPE_HEADER){returnnewRecyclerView.ViewHolder(header){};}elseif(viewType==TYPE_FOOTER){returnnewRecyclerView.ViewHolder(footer){};}ViewitemView=inflater.inflate(R.layout.recycler_item,null);returnnewMyViewHolder(itemView);}//给每一行View填充数据@OverridepublicvoidonBindViewHolder(RecyclerView.ViewHolderholder,intposition){if(getItemViewType(position)==TYPE_HEADER||getItemViewType(position)==TYPE_FOOTER){return;}MyViewHoldermyholder=(MyViewHolder)holder;myholder.textview.setText(datas.get(getRealPosition(position)));}//如果有头部position的位置是从1开始的所以需要-1publicintgetRealPosition(intposition){returnheader==null?position:position-1;}//数据源的数量@OverridepublicintgetItemCount(){if(header==null&&footer==null){//没有head跟footreturndatas.size();}elseif(header==null&&footer!=null){//head为空&&foot不为空returndatas.size()+1;}elseif(header!=null&&footer==null){//head不为空&&foot为空returndatas.size()+1;}else{returndatas.size()+2;//head不为空&&foot不为空}}@OverridepublicintgetItemViewType(intposition){//如果头布局不为空&&位置是第一个那就是head类型if(header!=null&&position==0){returnTYPE_HEADER;}elseif(footer!=null&&position==getItemCount()-1){//如果footer不为空&&最后一个returnTYPE_FOOTER;}returnsuper.getItemViewType(position);}publicvoidsetHeader(Viewheader){this.header=header;notifyItemInserted(0);//在位置0插入一条数据,然后刷新}publicvoidsetFooter(Viewfooter){this.footer=footer;notifyItemInserted(datas.size()-1);//在尾部插入一条数据,然后刷新}classMyViewHolderextendsRecyclerView.ViewHolder{privateTextViewtextview;publicMyViewHolder(ViewitemView){super(itemView);textview=(TextView)itemView.findViewById(R.id.textview);}}}
getItemCount
有header跟footer的时候需要在源数据长度基础上进行增加。
getItemViewType
通过getItemViewType判断不同的类型
onCreateViewHolder
通过不同的类型创建item的View
onBindViewHolder
如果是header跟footer类型是不需要绑定数据的,header跟footer的View一般在actvity中创建,不需要这边做处理,所以这两种类型我们就不往下执行,如果有头布局,position==0的位置被header占用了,但是我们的数据源也就是集合的下标是从0开始的,所以这里需要-1。
setHeader
设置头布局,在第一行插入一条数据,然后刷新。注意这个方法调用后会有插入的动画,这个动画可以使用默认的,也可以自己定义
setFooter
设置尾部布局,在尾部插入一条数据,然后刷新。
添加header跟footer的方法终于封装好了,在activity中只需要两行代码就能添加header,跟ListView调用addHeader方法一样简单,又可以happy的玩耍了。这里需要注意的是我们初始化View的时候,inflate方法需要三个参数。
resource 资源id
root 父View
attachToRoot true:返回父View false:返回资源id生成的View
//添加headerViewheader=LayoutInflater.from(this).inflate(R.layout.recycler_header,recyclerView,false);adapter.setHeader(header);//添加footerViewfooter=LayoutInflater.from(this).inflate(R.layout.recycler_footer,recyclerView,false);adapter.setFooter(footer);
recycler_header跟recycler_footer布局文件我就不贴出来了,就一个TextView,我们直接看效果图:
当我们调用RecyclerView的setOnItemClickListener方法的时候,发现居然没有,用了RecyclerView你要习惯什么东西都自己封装。。。
首先我们从adapter开刀,内部写一个接口,一个实例变量,提供一个公共方法,设置监听。
privateRecyclerViewItemClickrecyclerViewItemClick;publicvoidsetRecyclerViewItemClick(RecyclerViewItemClickrecyclerViewItemClick){this.recyclerViewItemClick=recyclerViewItemClick;}publicinterfaceRecyclerViewItemClick{/***item点击*@paramrealPosition数据源position*@parampositionviewposition*/voidonItemClick(intrealPosition,intposition);}
在onBindViewHolder方法中给item监听点击事件
if(recyclerViewItemClick!=null){myholder.itemView.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(Viewv){recyclerViewItemClick.onItemClick(getRealPosition(position),position);}});}
在activity的onCreate方法中进行监听,顺便设置item增加删除动画。我用的是sdk自带的默认动画。
adapter.setRecyclerViewItemClick(recyclerViewItemClick);recyclerView.setItemAnimator(newDefaultItemAnimator());
privateRecyclerHeadFootViewAdapter.RecyclerViewItemClickrecyclerViewItemClick=newRecyclerHeadFootViewAdapter.RecyclerViewItemClick(){@OverridepublicvoidonItemClick(intrealPosition,intposition){Log.i("ansen","删除数据:"+realPosition+"view位置:"+position);Log.i("ansen","当前位置:"+position+"更新item数量:"+(adapter.getItemCount()-position-1));datas.remove(realPosition);//删除数据源adapter.notifyItemRemoved(position);//item移除动画//更新position至adapter.getItemCount()-1的数据adapter.notifyItemRangeChanged(position,adapter.getItemCount()-position-1);}};
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。