Android开发中RecyclerView组件使用的一些进阶技讲解
RecyclerView的优势:
- 它自带ViewHolder来实现View的复用机制,再也不用ListView那样在getView()里自己写了
- 使用LayoutManager可以实现ListView,GridView以及流式布局的列表效果
- 通过setItemAnimator(ItemAnimatoranimator)可以实现增删动画(懒的话,可以使用默认的ItemAnimator对象,效果也不错)
- 控制item的间隔,可以使用addItemDecoration(ItemDecorationdecor),不过里边的ItemDecoration是一个抽象类,需要自己去实现...
用法介绍:
导入RecyclerView的v7库:
RecyclerView是一个android.support.v7库里的控件,因此在使用的时候我们需要在gradle配置文件里加上compile'com.android.support:recyclerview-v7:22.2.1'来引入google官方的这个库
xml布局中,使用常规的控件引入方式,来引入RecyclerView,如下:
<android.support.v7.widget.RecyclerView android:id="@+id/recyclerview_content" style="?recyclerview_style" android:scrollbars="vertical" android:fadeScrollbars="true" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="-55dp"/>
代码中的写法基本和ListView相差无几,但还是要重点说一下:
在实例化RecyclerView之后,我们需要使用setLayoutManager()给它设置布局管理器,其中的实参即就是LayoutManager,这里总共有两种LayoutManager:
1.StaggeredGridLayoutManager,是我们之前提到的流式布局:
它有一个构造方法StaggeredGridLayoutManager(intspanCount,intorientation),第一个是网格的列数,第二个参数是数据呈现的方向
(如果是竖直,那么第一个参数的意义就是列数,反之为行数。而且第二个参数在StaggeredGridLayoutManager里也有同样名称的常量,请同学们自行采纳[这里还是建议大家使用库里自带的常量,因为他们一般都是整型值,这样可以避免各个类里所判别的常量值不一样而导致的其他问题]);
2.GridLayoutManager,网格布局(流式布局应该是它的一个特殊情况):
GridLayoutManager(Contextcontext,intspanCount)或
GridLayoutManager(Contextcontext,intspanCount,intorientation,
booleanreverseLayout)需要说明的是,最后一个参数表示的是是否逆向布局(意思是将数据反向显示,原先从左向右,从上至下。设为true之后全部逆转)。
小提示:在这两个LayoutManager中,默认的orientation为vertical,reverseLayout为false。对应的参数在GridLayoutManager中都有对应的方法来进行补充设置。而在StaggeredGridLayoutManager中所有的方法都针对reverseLayout做了判断,然而它并没有给出这个参数设定值的api。
线性布局,LinearLayoutManager
LinearLayoutManager(Contextcontext)构建一个默认布局方向为VERTICAL的RecyclerView,这种样式也是ListView默认的。
LinearLayoutManager(Contextcontext,intorientation,booleanreverseLayout)。发现没有,线性布局和网格布局几乎是一样的。只是少了一个spanCount参数。
和ListView一样,为RecyclerView设置Adapter
classHomeAdapterextendsRecyclerView.Adapter<HomeAdapter.MyViewHolder> { @Override publicMyViewHolderonCreateViewHolder(ViewGroupparent,intviewType) { MyViewHolderholder=newMyViewHolder(LayoutInflater.from( HomeActivity.this).inflate(R.layout.item_home,parent, false)); returnholder; } @Override publicvoidonBindViewHolder(MyViewHolderholder,intposition) { holder.tv.setText(mDatas.get(position)); } @Override publicintgetItemCount() { returnmDatas.size(); } classMyViewHolderextendsViewHolder { TextViewtv; publicMyViewHolder(Viewview) { super(view); tv=(TextView)view.findViewById(R.id.id_num); } } }
如上,即就是Adapter的写法。其中的ViewHolder就是拿来负责View的回收和复用的,这样就不需要我们自己写完ViewHolder之后,还要在getView(intposition,ViewconvertView,ViewGroupparent)里一顿判断,一顿绑定,一顿find了。而且这里的ViewHolder成为了RecyclerView中必须继承的一部分,重写完后就需要放入RecyclerView.Adapter<>这里来对基类的范型初始化。
在这里,Recyclerview已经为你封装好了:
- getItemCount()就不必多说了,和ListView是一样的
- getItemViewType(intposition)是用来根据position的不同来实现RecyclerView中对不同布局的要求。从这个方法中所返回的值会在onCreateViewHolder中用到。比如头部,尾部,等等的特殊itemView(这里说成ViewHolder比较好)都可以在这里进行判断。
- onCreateViewHolder(ViewGroupparent,intviewType)是用来配合写好的ViewHolder来返回一个ViewHolder对象。这里也涉及到了条目布局的加载。viewType则表示需要给当前position生成的是哪一种ViewHolder,这个参数也说明了RecyclerView对多类型ItemView的支持。
- onBindViewHolder(MyViewHolderholder,intposition)专门用来绑定ViewHolder里的控件和数据源中position位置的数据。
这里,会有人问,那么item的子控件findViewById去哪儿了?我们把它交给了ViewHolder的构造方法(其他方法也可以),它的本质是在onCreateViewHolder方法里生成ViewHolder的时候执行的。
提升:
1.代码重构:
在上边看了这么多,有木有觉得,ViewHolder的功能并不是非常明确?它既负责了子控件的查询,又负责了子控件的装载工作。而布局加载和数据绑定却交给了Adapter......
我们来看看掘金的做法:
首先,它把Adapter和ViewHolder的功能以一种较为低耦合的方式进行了职能分离,让ViewHolder里所有的逻辑代码全部都只出现在ViewHolder中。我们现在就对前边提到的代码进行重构:
ViewHodler:
classMyViewHolderextendsViewHolder{ TextViewtv; publicMyViewHolder(Contextcontext,Viewview){ super(view); tv=(TextView)view.findViewById(R.id.id_num); //当然,我们也可以在这里使用view来对RecyclerView的一个item进行事件监听,也可以使用 //tv等子控件来实现item的子控件的事件监听。这也是我之所以要传context的原因之一呢~ ...... } publicstaticMyViewHoldernewInstance(Activitycontext,ViewGroupparent){ Viewview=LayoutInflater.from( context).inflate(R.layout.item_home,parent,false); returnnewMyViewHolder(context,view); } } //你没看错,数据绑定也被整合进来了, //将adapter里的数据根据position获取到后传进来。当然,也可以根据具体情况来做调整。 publicvoidonBinViewHolder(Stringdata){ tv.setText(data);//既然这里也有子控件,那么这里也可以做item子控件的事件监听喽 }
RecyclerView:
classHomeAdapterextendsRecyclerView.Adapter<MyViewHolder>{ @Override publicMyViewHolderonCreateViewHolder(ViewGroupparent,intviewType){//如需要,还要对viewType做判断 returnMyViewHolder.newInstance(this,parent) } @Override publicvoidonBindViewHolder(MyViewHolderholder,intposition){ holder.onBindViewHolder(mDatas.get(position)); } @Override publicintgetItemCount(){ returnmDatas.size(); } }
抽取一个条目点击事件,让它更像ListView:
classHomeAdapterextendsRecyclerView.Adapter<MyViewHolder>{ privateOnItemClickListenermOnItemClickListener; publicvoidsetOnItemClickLitener(OnItemClickLitenermOnItemClickLitener) { this.mOnItemClickLitener=mOnItemClickLitener; } @Override publicMyViewHolderonCreateViewHolder(ViewGroupparent,intviewType){//如需要,还要对viewType做判断 returnMyViewHolder.newInstance(this,parent) } @Override publicvoidonBindViewHolder(MyViewHolderholder,intposition){ holder.onBindViewHolder(mDatas.get(position)); //如果设置了回调,则设置点击事件 if(mOnItemClickLitener!=null){ viewHolder.itemView.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewv){ mOnItemClickLitener.onItemClick(viewHolder.itemView,i); } }); } } @Override publicintgetItemCount(){ returnmDatas.size(); } publicinterfaceOnItemClickLitener{ voidonItemClick(Viewview,intposition); } }
接口调用
mAdapter.setOnItemClickLitener(newOnItemClickLitener(){ @Override publicvoidonItemClick(Viewview,intposition){ ...... } });
2.External
上边提到了
控制item的间隔,可以使用addItemDecoration(ItemDecorationdecor),不过里边的ItemDecoration是一个抽象类,需要自己去实现...
这个问题,那我们来实际解决一下:
这是羊神实现的一个子类
packagecom.zhy.sample.demo_recyclerview; importandroid.content.Context; importandroid.content.res.TypedArray; importandroid.graphics.Canvas; importandroid.graphics.Rect; importandroid.graphics.drawable.Drawable; importandroid.support.v7.widget.LinearLayoutManager; importandroid.support.v7.widget.RecyclerView; importandroid.support.v7.widget.RecyclerView.State; importandroid.util.Log; importandroid.view.View; publicclassDividerItemDecorationextendsRecyclerView.ItemDecoration{ privatestaticfinalint[]ATTRS=newint[]{ android.R.attr.listDivider }; publicstaticfinalintHORIZONTAL_LIST=LinearLayoutManager.HORIZONTAL; publicstaticfinalintVERTICAL_LIST=LinearLayoutManager.VERTICAL; privateDrawablemDivider; privateintmOrientation; publicDividerItemDecoration(Contextcontext,intorientation){ finalTypedArraya=context.obtainStyledAttributes(ATTRS); mDivider=a.getDrawable(0); a.recycle(); setOrientation(orientation); } publicvoidsetOrientation(intorientation){ if(orientation!=HORIZONTAL_LIST&&orientation!=VERTICAL_LIST){ thrownewIllegalArgumentException("invalidorientation"); } mOrientation=orientation; } @Override publicvoidonDraw(Canvasc,RecyclerViewparent){ Log.v("recyclerview-itemdecoration","onDraw()"); if(mOrientation==VERTICAL_LIST){ drawVertical(c,parent); }else{ drawHorizontal(c,parent); } } publicvoiddrawVertical(Canvasc,RecyclerViewparent){ finalintleft=parent.getPaddingLeft(); finalintright=parent.getWidth()-parent.getPaddingRight(); finalintchildCount=parent.getChildCount(); for(inti=0;i<childCount;i++){ finalViewchild=parent.getChildAt(i); android.support.v7.widget.RecyclerViewv=newandroid.support.v7.widget.RecyclerView(parent.getContext()); finalRecyclerView.LayoutParamsparams=(RecyclerView.LayoutParams)child .getLayoutParams(); finalinttop=child.getBottom()+params.bottomMargin; finalintbottom=top+mDivider.getIntrinsicHeight(); mDivider.setBounds(left,top,right,bottom); mDivider.draw(c); } } publicvoiddrawHorizontal(Canvasc,RecyclerViewparent){ finalinttop=parent.getPaddingTop(); finalintbottom=parent.getHeight()-parent.getPaddingBottom(); finalintchildCount=parent.getChildCount(); for(inti=0;i<childCount;i++){ finalViewchild=parent.getChildAt(i); finalRecyclerView.LayoutParamsparams=(RecyclerView.LayoutParams)child .getLayoutParams(); finalintleft=child.getRight()+params.rightMargin; finalintright=left+mDivider.getIntrinsicHeight(); mDivider.setBounds(left,top,right,bottom); mDivider.draw(c); } } @Override publicvoidgetItemOffsets(RectoutRect,intitemPosition,RecyclerViewparent){ if(mOrientation==VERTICAL_LIST){ outRect.set(0,0,0,mDivider.getIntrinsicHeight()); }else{ outRect.set(0,0,mDivider.getIntrinsicWidth(),0); } } }
然后就是为我们的RecyclerView实例添加分割线
addItemDecoration(newDividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
系统默认的分割线往往达不到我们产品和设计师的要求,怎么办呢?
<itemname="android:listDivider">@drawable/your_custom_divider</item>
通过以上xml属性,将系统的listDivider设为自己画出来的分割线,只需要放在你对应activity的主题下即可。
技巧:RecyclerView滚动条的显示与隐藏
<android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="vertical" > </android.support.v7.widget.RecyclerView>
纵向显示:
android:scrollbars="vertical"
横向显示:
android:scrollbars="horizontal"
隐藏:
android:scrollbars="none"