compile "com.android.support:recyclerview-v7:23.1.0"
public class LinearLayoutActivity extends AppCompatActivity { private RecyclerView recyclerView; private RecyclerViewAdapter adapter; private List<String> datas; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.recycler_main); initData(); recyclerView= (RecyclerView) findViewById(R.id.recyclerview); recyclerView.setLayoutManager(new LinearLayoutManager(this));//设置布局管理器 recyclerView.addItemDecoration(new DividerItemDecoration(this)); recyclerView.setAdapter(adapter=new RecyclerViewAdapter(this,datas)); } private void initData(){ datas=new ArrayList<>(); for(int i=0;i<100;i++){datas.add("item:"+i); } }}activity对应的布局文件:recycler_main.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="match_parent" /></RelativeLayout>adapter相对ListView来说变化比较大的。把ViewHolder逻辑封装起来了,代码相对简单一些。
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder>{private List<String> datas;private LayoutInflater inflater;public RecyclerViewAdapter(Context context,List<String> datas){ inflater=LayoutInflater.from(context); this.datas=datas;}//创建每一行的View 用RecyclerView.ViewHolder包装@Overridepublic RecyclerViewAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView=inflater.inflate(R.layout.recycler_item,null); return new MyViewHolder(itemView);}//给每一行View填充数据@Overridepublic void onBindViewHolder(RecyclerViewAdapter.MyViewHolder holder, int position) { holder.textview.setText(datas.get(position));}//数据源的数量@Overridepublic int getItemCount() { return datas.size();}class MyViewHolder extends RecyclerView.ViewHolder{ private TextView textview; public MyViewHolder(View itemView) { super(itemView); textview= (TextView) itemView.findViewById(R.id.textview); }}}我们来看看效果图:
RecyclerView增加分隔线
RecyclerView是没有android:divider跟android:dividerHeight属性的,如果我们需要分割线,就只能自己动手去实现了。
public class DividerItemDecoration extends RecyclerView.ItemDecoration{ /* * RecyclerView的布局方向,默认先赋值 为纵向布局 * RecyclerView 布局可横向,也可纵向 * 横向和纵向对应的分割线画法不一样 * */ private int mOrientation = LinearLayoutManager.VERTICAL; private int mItemSize = 1;//item之间分割线的size,默认为1 private Paint mPaint;//绘制item分割线的画笔,和设置其属性 public DividerItemDecoration(Context context) { this(context,LinearLayoutManager.VERTICAL,R.color.colorAccent); } public DividerItemDecoration(Context context, int orientation) { this(context,orientation, R.color.colorAccent); } public DividerItemDecoration(Context context, int orientation, int dividerColor){ this(context,orientation,dividerColor,1); } /** * @param context * @param orientation 绘制方向 * @param dividerColor 分割线颜色 颜色资源id * @param mItemSize 分割线宽度 传入dp值就行 */ public DividerItemDecoration(Context context, int orientation, int dividerColor, int mItemSize){ this.mOrientation = orientation; if(orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL){throw new IllegalArgumentException("请传入正确的参数") ; } //把dp值换算成px this.mItemSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,mItemSize,context.getResources().getDisplayMetrics()); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(context.getResources().getColor(dividerColor)); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { if(mOrientation == LinearLayoutManager.VERTICAL){drawVertical(c,parent) ; }else {drawHorizontal(c,parent) ; } } /** * 绘制纵向 item 分割线 * @param canvas * @param parent */ private void drawVertical(Canvas canvas,RecyclerView parent){ final int left = parent.getPaddingLeft() ; final int right = parent.getMeasuredWidth() - parent.getPaddingRight(); final int childSize = parent.getChildCount() ; for(int i = 0 ; i < childSize ; i ++){final View child = parent.getChildAt( i ) ;RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();final int top = child.getBottom() + layoutParams.bottomMargin ;final int bottom = top + mItemSize ;canvas.drawRect(left,top,right,bottom,mPaint); } } /** * 绘制横向 item 分割线 * @param canvas * @param parent */ private void drawHorizontal(Canvas canvas,RecyclerView parent){ final int top = parent.getPaddingTop() ; final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom() ; final int childSize = parent.getChildCount() ; for(int i = 0 ; i < childSize ; i ++){final View child = parent.getChildAt( i ) ;RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();final int left = child.getRight() + layoutParams.rightMargin ;final int right = left + mItemSize ;canvas.drawRect(left,top,right,bottom,mPaint); } } /** * 设置item分割线的size * @param outRect * @param view * @param parent * @param state */ @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if(mOrientation == LinearLayoutManager.VERTICAL){outRect.set(0,0,0,mItemSize);//垂直排列 底部偏移 }else {outRect.set(0,0,mItemSize,0);//水平排列 右边偏移 } }}不要忘记调用addItemDecoration方法哦
recyclerView.addItemDecoration(new DividerItemDecoration(this));//添加分割线
大家读到这里肯定会有一个疑问,这货比ListView麻烦多了啊,但是google官方为什么要说是ListView的升级版呢?接下来开始放大招。。。
GridLayoutManager
在RecyclerView中实现不同的列表,只需要切换不同的LayoutManager即可。RecyclerView.LayoutManager跟RecyclerView.ItemDecoration一样,都是RecyclerView静态抽象内部类,但是LayoutManager有三个官方写好的实现类。
recyclerView.setLayoutManager(new GridLayoutManager(this,2));
recyclerView.setLayoutManager(new GridLayoutManager(this,4,GridLayoutManager.HORIZONTAL,false));
public class DividerGridItemDecoration extends RecyclerView.ItemDecoration { /* * RecyclerView的布局方向,默认先赋值 为纵向布局 * RecyclerView 布局可横向,也可纵向 * 横向和纵向对应的分割线画法不一样 * */ private int mOrientation = LinearLayoutManager.VERTICAL; private int mItemSize = 1;//item之间分割线的size,默认为1 private Paint mPaint;//绘制item分割线的画笔,和设置其属性 public DividerGridItemDecoration(Context context) { this(context,LinearLayoutManager.VERTICAL,R.color.colorAccent); } public DividerGridItemDecoration(Context context, int orientation) { this(context,orientation, R.color.colorAccent); } public DividerGridItemDecoration(Context context, int orientation, int dividerColor){ this(context,orientation,dividerColor,1); } /** * @param context * @param orientation 绘制方向 * @param dividerColor 分割线颜色 颜色资源id * @param mItemSize 分割线宽度 传入dp值就行 */ public DividerGridItemDecoration(Context context, int orientation, int dividerColor, int mItemSize){ this.mOrientation = orientation; if(orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL){throw new IllegalArgumentException("请传入正确的参数") ; } //把dp值换算成px this.mItemSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,mItemSize,context.getResources().getDisplayMetrics()); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(context.getResources().getColor(dividerColor)); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { drawHorizontal(c, parent); drawVertical(c, parent); } private int getSpanCount(RecyclerView parent) { // 列数 int spanCount = -1; RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) {spanCount = ((GridLayoutManager) layoutManager).getSpanCount(); } else if (layoutManager instanceof StaggeredGridLayoutManager) {spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount(); } return spanCount; } public void drawHorizontal(Canvas canvas, RecyclerView parent) { int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) {final View child = parent.getChildAt(i);final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();final int left = child.getLeft() - params.leftMargin;final int right = child.getRight() + params.rightMargin + mItemSize;final int top = child.getBottom() + params.bottomMargin;final int bottom = top + mItemSize;canvas.drawRect(left,top,right,bottom,mPaint); } } public void drawVertical(Canvas canvas, RecyclerView parent) { final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) {final View child = parent.getChildAt(i);final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();final int top = child.getTop() - params.topMargin;final int bottom = child.getBottom() + params.bottomMargin;final int left = child.getRight() + params.rightMargin;final int right = left + mItemSize;canvas.drawRect(left,top,right,bottom,mPaint); } } @Override public void getItemOffsets(Rect outRect, int itemPosition,RecyclerView parent) { int spanCount = getSpanCount(parent); int childCount = parent.getAdapter().getItemCount(); if (isLastRow(parent, itemPosition, spanCount, childCount)){//如果是最后一行,不需要绘制底部outRect.set(0, 0, mItemSize, 0); } else if (isLastColum(parent, itemPosition, spanCount, childCount)){// 如果是最后一列,不需要绘制右边outRect.set(0, 0, 0, mItemSize); } else {outRect.set(0, 0, mItemSize,mItemSize); } } private boolean isLastColum(RecyclerView parent, int pos, int spanCount, int childCount) { RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) {if ((pos + 1) % spanCount == 0){// 如果是最后一列,则不需要绘制右边return true;} } else if (layoutManager instanceof StaggeredGridLayoutManager) {int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();if (orientation == StaggeredGridLayoutManager.VERTICAL) {if ((pos + 1) % spanCount == 0){// 如果是最后一列,则不需要绘制右边 return true;}} else {childCount = childCount - childCount % spanCount;if (pos >= childCount)// 如果是最后一列,则不需要绘制右边 return true;} } return false; } private boolean isLastRow(RecyclerView parent, int pos, int spanCount, int childCount) { RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) {childCount = childCount - childCount % spanCount;if (pos >= childCount)//最后一行return true; } else if (layoutManager instanceof StaggeredGridLayoutManager) {int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();if (orientation == StaggeredGridLayoutManager.VERTICAL){//纵向childCount = childCount - childCount % spanCount;if (pos >= childCount)//最后一行 return true;} else{ //横向if ((pos + 1) % spanCount == 0) {//是最后一行 return true;}} } return false; }}写了这两个画分割线的类,主流的布局:线性列表跟网格列表都能展示了。。。赶紧运行代码看看结果:
StaggeredGridLayoutManager
actviity中修改下布局管理器,大家应该感觉很熟悉了吧~~~
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL));瀑布流列表一般列的高度是不一致的,为了模拟不同的宽高,数据源我把String类型改成了对象.然后初始化的时候随机了一个高度.
public class ItemData { private String content;//item内容 private int height;//item高度 public ItemData() { } public ItemData(String content, int height) { this.content = content; this.height = height; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; }}瀑布流列表没有添加分割线,给item布局设置了android:padding属性。recycler_staggered_item.xml
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:padding="5dp" android:layout_width="wrap_content" android:layout_height="match_parent"> <TextView android: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设置一个高度
@Overridepublic void onBindViewHolder(StaggeredGridAdapter.MyViewHolder holder, int position) { ItemData itemData=datas.get(position); holder.textview.setText(itemData.getContent()); //手动更改高度,不同位置的高度有所不同 holder.textview.setHeight(itemData.getHeight());}是不是感觉so easy,赶紧运行看看效果:
添加header跟footer
RecyclerView添加头部跟底部是没有对应的api的,但是我们很多的需求都会用到,于是只能自己想办法实现了。我们可以通过适配器的getItemViewType方法来实现这个功能。
修改后的适配器代码:RecyclerHeadFootViewAdapter.java
public class RecyclerHeadFootViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{ private List<String> datas; private LayoutInflater inflater; public static final int TYPE_HEADER=1;//header类型 public static final int TYPE_FOOTER=2;//footer类型 private View header=null;//头View private View footer=null;//脚View public RecyclerHeadFootViewAdapter(Context context, List<String> datas){ inflater=LayoutInflater.from(context); this.datas=datas; } //创建每一行的View 用RecyclerView.ViewHolder包装 @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(viewType==TYPE_HEADER){return new RecyclerView.ViewHolder(header){}; }else if(viewType==TYPE_FOOTER){return new RecyclerView.ViewHolder(footer){}; } View itemView=inflater.inflate(R.layout.recycler_item,null); return new MyViewHolder(itemView); } //给每一行View填充数据 @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position){ if(getItemViewType(position)==TYPE_HEADER||getItemViewType(position)==TYPE_FOOTER){return; } MyViewHolder myholder= (MyViewHolder) holder; myholder.textview.setText(datas.get(getRealPosition(position))); } //如果有头部 position的位置是从1开始的 所以需要-1 public int getRealPosition(int position){ return header==null?position:position-1; } //数据源的数量 @Override public int getItemCount() { if(header == null && footer == null){//没有head跟footreturn datas.size(); }else if(header == null && footer != null){//head为空&&foot不为空return datas.size() + 1; }else if (header != null && footer == null){//head不为空&&foot为空return datas.size() + 1; }else {return datas.size() + 2;//head不为空&&foot不为空 } } @Override public int getItemViewType(int position){ //如果头布局不为空&&位置是第一个那就是head类型 if(header!=null&&position==0){return TYPE_HEADER; }else if(footer!=null&&position==getItemCount()-1){//如果footer不为空&&最后一个return TYPE_FOOTER; } return super.getItemViewType(position); } public void setHeader(View header) { this.header = header; notifyItemInserted(0);//在位置0插入一条数据,然后刷新 } public void setFooter(View footer) { this.footer = footer; notifyItemInserted(datas.size()-1);//在尾部插入一条数据,然后刷新 } class MyViewHolder extends RecyclerView.ViewHolder{ private TextView textview; public MyViewHolder(View itemView) {super(itemView);textview= (TextView) itemView.findViewById(R.id.textview); } }}getItemCount
//添加headerView header=LayoutInflater.from(this).inflate(R.layout.recycler_header,recyclerView,false);adapter.setHeader(header);//添加footerView footer=LayoutInflater.from(this).inflate(R.layout.recycler_footer,recyclerView,false);adapter.setFooter(footer);recycler_header跟recycler_footer布局文件我就不贴出来了,就一个TextView,我们直接看效果图:
item点击事件&&增加或删除带动画效果
当我们调用RecyclerView的setOnItemClickListener方法的时候,发现居然没有,用了RecyclerView你要习惯什么东西都自己封装。。。
首先我们从adapter开刀,内部写一个接口,一个实例变量,提供一个公共方法,设置监听。
private RecyclerViewItemClick recyclerViewItemClick;public void setRecyclerViewItemClick(RecyclerViewItemClick recyclerViewItemClick) { this.recyclerViewItemClick = recyclerViewItemClick;}public interface RecyclerViewItemClick{ /** * item点击 * @param realPosition 数据源position * @param position view position */ void onItemClick(int realPosition,int position);}在onBindViewHolder方法中给item监听点击事件
if(recyclerViewItemClick!=null) { myholder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) {recyclerViewItemClick.onItemClick(getRealPosition(position),position); } });}在activity的onCreate方法中进行监听,顺便设置item增加删除动画。我用的是sdk自带的默认动画。
adapter.setRecyclerViewItemClick(recyclerViewItemClick);recyclerView.setItemAnimator(new DefaultItemAnimator());
private RecyclerHeadFootViewAdapter.RecyclerViewItemClick recyclerViewItemClick=new RecyclerHeadFootViewAdapter.RecyclerViewItemClick() { @Override public void onItemClick(int realPosition, int position) { 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); }};源码下载