点此进入:从零快速构建APP系列目录导图
点此进入:UI编程系列目录导图
点此进入:四大组件系列目录导图
点此进入:数据网络和线程系列目录导图

一、RecycleView 简介

(1)RecycleView是什么

RecyclerView 出现已经有一段时间了,相信大家肯定不陌生了,不过这里还是简单介绍下。

RecylerView是Android L版本中新添加的一个用来取代ListView的SDK,是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字recylerview即“回收view”也可以看得出来,毫无疑问它的灵活性与可替代性比listview更好。

看到这也许有人会问:除此之外还有什么优点呢?我们为什么一定要用RecylerView呢?下面我们详细介绍下RecylerView的诸多优点。

(2)RecyclerView的优点是什么?

根据官方的介绍RecylerView是ListView的升级版,既然如此那么RecylerView必然有它的优点,现就RecylerView相对于ListView的优点罗列如下:

1、RecylerView封装了viewholder的回收复用。

也就是说 RecylerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单

2、提供了一种插拔式的体验,高度的解耦,异常的灵活。

针对一个Item的显示,RecylerView专门抽取出了相应的类来控制Item的显示,使其的扩展性非常强。

比如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制,与GridView效果对应的是GridLayoutManager,与瀑布流对应的还有StaggeredGridLayoutManager等,也就是说RecylerView不再拘泥于ListView的线性展示方式,它也可以实现ListView、GridView等多种效果。再比如你想控制Item的分隔线,可以通过继承RecylerView的ItemDecoration这个类,然后针对自己的业务需求去写代码。

总之,通过简单改变下LayoutManager,就可以产生很多不同的效果,那么我们可以根据手机屏幕的宽度去动态设置LayoutManager,屏幕宽度一般的,显示为ListView,宽度稍大的显示两列的GridView或者瀑布流,或者横纵屏幕切换时变化,显示的列数和宽度成正比。甚至某些特殊屏幕,让其横向滑动,再选择一个好的动画效果,可以达到前所未有的用户体验。

3、可以控制Item增删的动画。

可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecylerView也有其自己默认的实现。

(3)RecyclerView项目结构


- Adapter:使用RecyclerView之前,你需要一个继承自RecyclerView.Adapter的适配器,作用是将数据与每一个item的界面进行绑定。
- LayoutManager:用来控制其显示的方式,比如:确定每一个item如何进行排列摆放,何时展示和隐藏。回收或重用一个View的时候,LayoutManager会向适配器请求新的数据来替换旧的数据,这种机制避免了创建过多的View和频繁的调用findViewById方法(与ListView原理类似)。
目前SDK中提供了三种自带的LayoutManager:
- LinearLayoutManager(线性布局管理器)
- GridLayoutManager(网格布局管理器)
- StaggeredGridLayoutManager(瀑布流布局管理器)

二、RecycleView 的简单使用

说了这么多,可能大家最关心的就是RecylerView应该怎么用,我们先来讨论讨论RecylerView的用法的理论知识,然后首先结合一个实例来展示是一个最简单的使用方法,然后介绍更多RecyclerView的用法。

1、添加依赖

在AS的build.gradle中添加依赖,然后同步一下就可以引入依赖包:

dependencies {...compile 'com.android.support:appcompat-v7:24.0.0'
}

2、编写代码

添加完依赖之后,就开始写代码了,与ListView用法类似,也是先在xml布局文件中创建一个RecyclerView的布局:
<RelativeLayout 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"tools:context=".MainActivity"><android.support.v7.widget.RecyclerViewandroid:id="@+id/my_recycler_view"android:layout_width="match_parent"android:layout_height="match_parent"android:scrollbars="vertical"/>
</RelativeLayout>
创建完布局之后在MainActivity中获取这个RecyclerView,并声明LayoutManager与Adapter,代码如下:
mRecyclerView = (RecyclerView)findViewById(R.id.my_recycler_view);
// 创建默认的线性LayoutManager
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
// 如果可以确定每个item的高度是固定的,设置这个选项可以提高性能
mRecyclerView.setHasFixedSize(true);
// 创建并设置Adapter
mAdapter = newMyAdapter(getDummyDatas());
mRecyclerView.setAdapter(mAdapter);

可以看到对RecylerView的设置过程,比ListView要复杂一些,因为ListView可能只需要去设置一个adapter就能正常使用了,这也是RecylerView高度解耦的表现,虽然代码抒写上有点复杂,但它的扩展性是极高的。也就是说RecyclerView只管回收与复用View,其他的你可以自己去设置。这就给予了我们充分定制的自由,所以我们才可以轻松的通过这个控件实现ListView、GirdView、瀑布流等效果。

接下来就是Adapter的创建:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {public String[] datas = null;public MyAdapter(String[] datas) {this.datas = datas;}// 创建新View,被LayoutManager所调用@Overridepublic ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item,viewGroup,false);ViewHolder vh = new ViewHolder(view);return vh;}// 将数据与界面进行绑定的操作@Overridepublic void onBindViewHolder(ViewHolder viewHolder, int position) {viewHolder.mTextView.setText(datas[position]);}// 获取数据的数量@Overridepublic int getItemCount() {return datas.length;}// 自定义的ViewHolder,持有每个Item的的所有界面元素public static class ViewHolder extends RecyclerView.ViewHolder {public TextView mTextView;public ViewHolder(View view){super(view);mTextView = (TextView) view.findViewById(R.id.text);}}
}

在了解了RecyclerView的一些控制之后,我们来看看它的Adapter的写法,RecyclerView的Adapter与ListView的Adapter还是有点区别的,RecyclerView.Adapter需要实现3个方法:
- onCreateViewHolder()
这个方法主要生成为每个Item inflater出一个View,但是该方法返回的是一个ViewHolder。该方法把View直接封装在ViewHolder中,然后我们面向的是ViewHolder这个实例,当然这个ViewHolder需要我们自己去编写。直接省去了当初的convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
- onBindViewHolder()
这个方法主要用于适配渲染数据到View中。方法提供给你了一个viewHolder,而不是原来的convertView。
- getItemCount()
这个方法就类似于BaseAdapter的getCount方法了,即总共有多少个条目。

可以看到RecyclerView标准化了ViewHolder,编写 Adapter面向的是ViewHoder而不在是View了,复用的逻辑被封装了,写起来更加简单。其实它的写法与BaseAdapter的写法是差不多的,大家可以对比下它与getView方法写法的区别,在onCreateViewHolder方法中初始化了一个View,然后返回一个ViewHolder,这个返回的ViewHolder类似于之前在getView中的convertView.getTag(),然后在onBindViewHolder方法中去给这个ViewHolder中的控件填充值。其实它的原理跟getView是差不多的,只是做了封装,我们写起来比较简洁。

编译运行效果如下:

三、RecycleView 添加点击和长击事件

上面我们讲了如何使用RecyclerView的Adpater,接下来我们为添加点击监听的功能。

熟悉ListView的小伙伴都知道ListView在使用的时候是可以直接添加点击事件的,就是说ListView给我们提供了一个onItemClickListener监听器,这样当我们点击Item的时候,会回调相关的方法,以便我们方便处理Item点击事件。但是对于RecyclerView来讲,非常可惜的是,该控件没有给我们提供ClickListener和LongClickListener这样的内置监听器方法,所以我们需要自己动手进行改造实现,只不过是会多了些代码而已。

实现的方式比较多,我们可以通过mRecyclerView.addOnItemTouchListener去监听然后去判断手势,
也可以通过adapter中自己去提供回调,这里我们选择最常用的后者来讲解,前者的方式大家有时间有兴趣自己去实现。

(1)首先我们在Adapter中创建一个实现点击接口:

其中view是点击的Item,data是我们的数据,因为我们想知道我点击的区域部分的数据是什么,以便我下一步进行操作:

public static interface OnRecyclerViewItemClickListener {void onItemClick(View view , DataModel data);
}
(2)定义完接口,添加接口和设置Adapter接口的方法:
private OnRecyclerViewItemClickListener mOnItemClickListener = null;
public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {this.mOnItemClickListener = listener;
}
(3)然后为Adapter实现OnClickListener方法:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener{@Overridepublic ViewHolder onCreateViewHolder(ViewGroup viewGroup, final int i) {View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);ViewHolder vh = new ViewHolder(view);// 将创建的View注册点击事件view.setOnClickListener(this);return vh;}@Overridepublic void onBindViewHolder(ViewHolder viewHolder, final int i) {viewHolder.mTextView.setText(datas.get(i).title);// 将数据保存在itemView的Tag中,以便点击时进行获取viewHolder.itemView.setTag(datas.get(i));}...@Overridepublic void onClick(View v) {if (mOnItemClickListener != null) {// 注意这里使用getTag方法获取数据mOnItemClickListener.onItemClick(v,(DataModel)v.getTag());}}...
}
当然我们也可以通过Recyclerview直接获取item位置信息:
        @Overridepublic void onClick(View v) {int position = mRecyclerview.getChildAdapterPosition(v);Log.i(TAG, "position : " + position);}

这样获取位置信息的方法依赖于Recyclerview,所以如果MyAdapter和当前界面不是在同一个界面的话就需要传过来一个Recyclerview实例,这样是比较麻烦的,一般来讲我们在mAdapter设置监听器的地方来获取即可,如下。

(4)最后在Activity或其他地方应用:
mAdapter = new MyAdapter(getDummyDatas());
mRecyclerView.setAdapter(mAdapter);
mAdapter.setOnItemClickListener(new MyAdapter.OnRecyclerViewItemClickListener() {@Overridepublic void onItemClick(View view, DataModel data) {// Do something.}
});

这样我们的点击事件监听器就设置完毕了,同理我们可以为RecyclerView设置长击事件,而且把两个接口融合到一起也是可以的,比如:

    public interface OnItemClickLitener {void onItemClick(View view, int position);void onItemLongClick(View view, int position);}

后续的步骤同点击事件是一样的,但是我们却可以在长击事件里面做更多的事情,比如我们熟悉的长按删除,可谓一举两得。

编译运行看效果:

四、RecycleView 添加和删除数据

以前在ListView当中,我们只要修改后数据用Adapter的notifyDatasetChange一下就可以更新界面。然而在RecyclerView中还有一些更高级的用法:

  • 添加数据:
public void addItem(DataModel content, int position) {datas.add(position, content);notifyItemInserted(position); //Attention!
}
  • 删除数据:
public void removeItem(DataModel model) {int position = datas.indexOf(model);datas.remove(position);notifyItemRemoved(position);//Attention!
}

改变数据后我们可以设置RecycleView滚动到相应位置:

mRecyclerview.scrollToPosition(position);

五、RecycleView 增加多样式分隔线

我们可以通过RecyclerView.addItemDecoration(ItemDecoration decoration)这个方法进行设置RecycleView的分割线,但是RecycleView的分隔线是不是强制地需要被设置呢?当然不是,因为我们上面的例子没有设置分隔线也没有报错,但是通过设置分割线可以让我们每一个Item从视觉上面相互分开来,例如ListView中divider哪种非常相似的效果。

可以看见,RecyclerView.addItemDecoration(ItemDecoration decoration)这个方法是一个ItemDecoration,而这个可以是我们自己定义的继承自ItemDecoration的一个对象,所以接下来我们创建一个继承RecyclerView.ItemDecoration类来绘制分隔线。

系统提供的ItemDecoration是一个抽象类,我们主要实现以下几个方法:
public static abstract class ItemDecoration {public void onDraw(Canvas c, RecyclerView parent, State state) {onDraw(c, parent);
}public void onDrawOver(Canvas c, RecyclerView parent, State state) {onDrawOver(c, parent);
}public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent);
}@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {outRect.set(0, 0, 0, 0);
}

(1)增加分隔线

既然我们为RecycleView设置了分隔线,那么 当我们的RecyclerView在进行绘制的时候一定会进行Decoration的绘制,那么就会去调用onDraw和onDrawOver方法,所以我们其实只要去重写onDraw(onDrawOver在drawChildren之后,一般我们选择复写其中一个即可)和getItemOffsets这两个方法就可以实现相应的分隔线绘制了。而在绘制好了之后,LayoutManager会进行Item的布局,这个时候就会去调用getItemOffset方法来计算每个Item的Decoration合适的尺寸,这就是分隔线在绘制的过程中各个方法的调用步骤 ,下面我们来实现一个:

public class DividerItemDecoration extends RecyclerView.ItemDecoration {private static final int[] ATTRS = new int[]{android.R.attr.listDivider};public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;private Drawable mDivider;private int mOrientation;public DividerItemDecoration(Context context, int orientation) {final TypedArray a = context.obtainStyledAttributes(ATTRS);mDivider = a.getDrawable(0);a.recycle();setOrientation(orientation);}public void setOrientation(int orientation) {if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {throw new IllegalArgumentException("invalid orientation");}mOrientation = orientation;}@Overridepublic void onDraw(Canvas c, RecyclerView parent) {Log.v("recyclerview - itemdecoration", "onDraw()");if (mOrientation == VERTICAL_LIST) {drawVertical(c, parent);} else {drawHorizontal(c, parent);}}public void drawVertical(Canvas c, RecyclerView parent) {final int left = parent.getPaddingLeft();final int right = parent.getWidth() - parent.getPaddingRight();final int childCount = parent.getChildCount();for (int i = 0; i < childCount; i++) {final View child = parent.getChildAt(i);android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();final int top = child.getBottom() + params.bottomMargin;final int bottom = top + mDivider.getIntrinsicHeight();mDivider.setBounds(left, top, right, bottom);mDivider.draw(c);}}public void drawHorizontal(Canvas c, RecyclerView parent) {final int top = parent.getPaddingTop();final int bottom = parent.getHeight() - parent.getPaddingBottom();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 left = child.getRight() + params.rightMargin;final int right = left + mDivider.getIntrinsicHeight();mDivider.setBounds(left, top, right, bottom);mDivider.draw(c);}}@Overridepublic void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {if (mOrientation == VERTICAL_LIST) {outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());} else {outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);}}
}

在这里我们是采用了系统主题(android.R.attr.listDivider)的方式来设置分隔线的(使用系统listDivider的好处是方便我们去随意的改变分隔线样式),然后来获取尺寸和位置进行setBound()绘制。接着通过outRect.set()来设置绘制整个区域范围,当然了它是有两种情况的,一种LinearLayoutManager.HORIZONTAL另外一种LinearLayoutManager.VERTICAL,我们需要分别对其进行处理。最后在RecyclerView中设置我们自定义的分割线,即在MainActivity中加上一句recyclerView .addItemDecoration(new DividerItemDecoration(MainActivity.this,LinearLayoutManager.VERTICAL)。

(2)改变样式

正如我们上面提到的,使用系统listDivider的好处是方便我们去随意的改变,因为该属性我们可以直接声明在这里:
 <!-- Application theme. --><style name="AppTheme" parent="AppBaseTheme"><item name="android:listDivider">@drawable/divider_bg</item>  </style>
然后我们可以自己写个drawable,以此实现分隔符的更换:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle" ><gradient
        android:centerColor="#ff00ff00"android:endColor="#ff0000ff"android:startColor="#ffff0000"android:type="linear" /><size android:height="4dp"/>
</shape>
或者这样:
<?xml version="1.0" encoding= "utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"  android:shape="rectangle" >  <!-- 填充的颜色 -->  <solid android:color ="@color/color_red"/>  <!--  线条大小 -->  <size android:height ="1dp" android:width ="1dp"/>
</shape>  

六、 两种方式为 RecyclerView 添加动画

(1)添加系统动画和自定义动画

控制RecyclerView增加和删除的动画是通过ItemAnimator这个类来实现的,ItemAnimator这类也是个抽象的类,系统默认给我们提供了一种增加和删除的动画,下面我们就来看看这种动画的效果,我们需要做的修改如下:

LinearLayoutManager layoutManager = new LinearLayoutManager(this);
// 设置布局管理器
recyclerView.setLayoutManager(layoutManager);
// 设置增加或删除条目的动画
recyclerView.setItemAnimator( new DefaultItemAnimator()); 

值得注意的是:我们这里更新数据集不是用 adapter.notifyDataSetChanged() 而是 notifyItemInserted(position) 与 notifyItemRemoved(position),否则是没有动画效果的,所以在Adapter中增加的代码如下:

public void addData(int position) {mDatas.add(position, "Insert One");notifyItemInserted(position);
}public void removeData(int position) {mDatas.remove(position);notifyItemRemoved(position);
}

然后在合适的地方调用addData和removeData就能看到相应的动画效果了。

我们这里只提供了一种动画,如果你想要去自定义各种更好玩的动画效果的话,可以自定义实现,也可以到github的许多类似的项目中借鉴经验,比如这里:RecyclerViewItemAnimators,有兴趣的同学可以自行查阅。

(2)使用ItemTouchHelper实现Item的拖拽和滑动删除以及动画效果

简介:

Google官方文档上是这么介绍的:
This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView. 这是一个支持RecyclerView滑动删除和拖拽的实用工具类。

我们自己的理解:
ItemTouchHelper是一个强大的工具,它是RecyclerView.ItemDecoration的子类,它处理好了关于在RecyclerView上添加拖动排序与滑动删除的所有事情,也就是说它可以轻易的添加到几乎所有的LayoutManager和Adapter中。除此之外,它还可以和现有的item动画一起工作,提供受类型限制的拖放动画等。

首先我们给出itemTouchHelper的整体实现,然后对其中各个方法做出解释。

itemTouchHelper的整体实现:
    itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {@Overridepublic int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {int dragFlags = 0, swipeFlags = 0;if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;} else if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) {dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;// 设置侧滑方向为从左到右和从右到左都可以swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;}return makeMovementFlags(dragFlags, swipeFlags);}@Overridepublic boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {int from = viewHolder.getAdapterPosition();int to = target.getAdapterPosition();Collections.swap(meizis, from, to);mAdapter.notifyItemMoved(from, to);return true;}@Overridepublic void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {mAdapter.removeItem(viewHolder.getAdapterPosition());}@Overridepublic void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {super.onSelectedChanged(viewHolder, actionState);if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {viewHolder.itemView.setBackgroundColor(Color.LTGRAY);}}@Overridepublic void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {super.clearView(recyclerView, viewHolder);viewHolder.itemView.setBackgroundColor(Color.WHITE);}@Overridepublic void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {viewHolder.itemView.setAlpha(1 - Math.abs(dX) / screenwidth);super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);}@Overridepublic boolean isLongPressDragEnabled() {return true;}@Overridepublic boolean isItemViewSwipeEnabled() {return true;}});
  • getMovementFlags()方法:
    ItemTouchHelper让我们可以轻易得到一个事件的方向,我们需要重写getMovementFlags()方法来指定可以支持的拖放和滑动的方向。使用helperItemTouchHelper.makeMovementFlags(int, int)来构造返回的flag,这里我们启用了上下左右两种方向。
    注:上下为拖动(drag),左右为滑动(swipe)。
  • isLongPressDragEnabled()方法:
    ItemTouchHelper可以用于没有滑动的拖动操作(或者反过来),你必须指明你到底要支持哪一种。要支持长按RecyclerView item进入拖动操作,你必须在isLongPressDragEnabled()方法中返回true。或者,也可以调用ItemTouchHelper.startDrag(RecyclerView.ViewHolder) 方法来开始一个拖动。
  • isItemViewSwipeEnabled()方法:
    要在view任意位置触摸事件发生时启用滑动操作,则直接在sItemViewSwipeEnabled()中返回true就可以了。或者,你也主动调用ItemTouchHelper.startSwipe(RecyclerView.ViewHolder) 来开始滑动操作。
  • onMove()和onSwiped()方法:
    用于通知底层数据的更新。
  • onSelectedChanged()、clearView()和onChildDraw()方法:
    设置拖拽和滑动时的响应动画效果。

七、几种不同的布局管理器

上面我们编写的类似ListView样子的Demo都是通过LinearLayoutManager来实现的,接下来我们介绍几种其它类型的LayoutManager。

系统提供了3个实现类:
  • LinearLayoutManager:线性管理器,支持横向、纵向。
  • GridLayoutManager:网格布局管理器。
  • StaggeredGridLayoutManager:瀑布流式布局管理器。

(1)实现网格布局

如果想把上面的线性布局改成网格布局,可以这样改写:

//mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setLayoutManager(new GridLayoutManager(this, 3));

但是改为GridLayoutManager以后,对于分割线前面的DividerItemDecoration就不再适用了,主要是因为它在绘制的时候,比如水平线针对每个child的取值为:

final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();

这样写对于每个Item一行的情况是没问题的,而GridLayoutManager一行有多个childItem,这样就会多次绘制,并且GridLayoutManager的Item如果为最后一列或者为最后一行时,也需要对边界条件进行处理,所以我们编写了DividerGridItemDecoration:

    public class DividerGridItemDecoration extends RecyclerView.ItemDecoration {private static final int[] ATTRS = new int[]{android.R.attr.listDivider};private Drawable mDivider;public DividerGridItemDecoration(Context context) {final TypedArray a = context.obtainStyledAttributes(ATTRS);mDivider = a.getDrawable(0);a.recycle();}@Overridepublic void onDraw(Canvas c, RecyclerView parent, State state) {drawHorizontal(c, parent);drawVertical(c, parent);}private int getSpanCount(RecyclerView parent) {// 列数int spanCount = -1;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 c, 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+ mDivider.getIntrinsicWidth();final int top = child.getBottom() + params.bottomMargin;final int bottom = top + mDivider.getIntrinsicHeight();mDivider.setBounds(left, top, right, bottom);mDivider.draw(c);}}public void drawVertical(Canvas c, 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 + mDivider.getIntrinsicWidth();mDivider.setBounds(left, top, right, bottom);mDivider.draw(c);}}private boolean isLastColum(RecyclerView parent, int pos, int spanCount, int childCount) {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 isLastRaw(RecyclerView parent, int pos, int spanCount, int childCount) {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();// StaggeredGridLayoutManager 且纵向滚动if (orientation == StaggeredGridLayoutManager.VERTICAL) {childCount = childCount - childCount % spanCount;// 如果是最后一行,则不需要绘制底部if (pos >= childCount)return true;} else// StaggeredGridLayoutManager 且横向滚动{// 如果是最后一行,则不需要绘制底部if ((pos + 1) % spanCount == 0) {return true;}}}return false;}@Overridepublic void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {int spanCount = getSpanCount(parent);int childCount = parent.getAdapter().getItemCount();if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最后一行,则不需要绘制底部{outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);} else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最后一列,则不需要绘制右边{outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());} else {outRect.set(0, 0, mDivider.getIntrinsicWidth(),mDivider.getIntrinsicHeight());}}}

也就是说我们需要在getItemOffsets方法中去判断是否是最后一行,如果是的话则不需要绘制底部;如果是最后一列的话则不需要绘制右边,并且整个判断也考虑到了StaggeredGridLayoutManager 的横向和纵向,所以稍稍有些复杂,一般如果仅仅是希望有空隙,还是去设置item的margin更方便。

(2)实现瀑布流布局

瀑布流布局其实也可以实现GridLayoutManager一样的功能,我们按照下列代码改写:

// mRecyclerView.setLayoutManager(new GridLayoutManager(this, 3));
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
可以看到效果为:

我们可以发现:通过RecyclerView去实现ListView、GridView、瀑布流的效果基本上没有什么区别,仅仅通过设置不同的LayoutManager即可实现不同的效果,这就是RecyclerView结合LayoutManager的强大之处。

联系方式:

简书:WillFlow
CSDN:WillFlow
微信公众号:WillFlow

点此进入:GitHub开源项目“爱阅”。“爱阅”专注于收集优质的文章、站点、教程,与大家共分享。下面是“爱阅”的效果图:


3.2.1 一篇文章完全掌握 RecycleView 的六大用法相关推荐

  1. 一篇文章告诉你标准化和归一化的区别?

    一篇文章告诉你标准化和归一化的区别? 2019-02-28 17:12:39 融融网融融网阅读量:484 进一步推进企业的标准化工作,使之发展水平适应经济全球化下市场竞争的要求,促进企业综合实力的提升 ...

  2. 一篇文章让你读懂Pivotal的GemFire家族产品

    一篇文章让你读懂Pivotal的GemFire家族产品 学习了:https://www.sohu.com/a/217157517_747818 转载于:https://www.cnblogs.com/ ...

  3. DEDECMS教程:上/下一篇文章标题长度的截取方法

    对dedecms了解的朋友们,想必对如何获取上一篇.下一篇文章的标签也是非常熟悉.dedecms获取上一篇.下一篇文章的标签分别为:{dede:prenext get='pre'/}.{dede:pr ...

  4. 一篇文章一张思维导图看懂Android学习最佳路线

    一篇文章一张思维导图看懂Android学习最佳路线 先上一张android开发知识点学习路线图思维导图 Android学习路线从4个阶段来对Android的学习过程做一个全面的分析:Android初级 ...

  5. UML科普文,一篇文章掌握14种UML图

    前言 上一篇文章写了一篇建造者模式,其中有几个UML类图,有的读者反馈看不懂了,我们今天就来解决一哈. 什么是UML? UML是Unified Model Language的缩写,中文是统一建模语言, ...

  6. 用一篇文章说清楚如何写作

    专门讲写作的书就有一大堆,这事能用一篇文章说清楚吗? 答案是能的,不信你往下看. 写之前要先弄清楚文章属于什么类型,类型不同写法当然不一样.以沟通为目的的文章最好写,虚构类文章不好写,因为你还要先虚构 ...

  7. 一篇文章告诉你如何成为数据科学家

    文章讲的是一篇文章告诉你如何成为数据科学家,通常来说,年轻人都很容易立志成为什么,例如成为一名科学家,然后又很快放弃.这一方面是因为摆在他们面前的诱惑太多,也因为成为一名科学家真的很不容易. 这一点放 ...

  8. Android:学习AIDL,这一篇文章就够了(下)

    前言 上一篇博文介绍了关于AIDL是什么,为什么我们需要AIDL,AIDL的语法以及如何使用AIDL等方面的知识,这一篇博文将顺着上一篇的思路往下走,接着介绍关于AIDL的一些更加深入的知识.强烈建议 ...

  9. Android:学习AIDL,这一篇文章就够了(上)

    前言 在决定用这个标题之前甚是忐忑,主要是担心自己对AIDL的理解不够深入,到时候大家看了之后说--你这是什么玩意儿,就这么点东西就敢说够了?简直是坐井观天不知所谓--那样就很尴尬了.不过又转念一想, ...

最新文章

  1. python能不能连等赋值_javascript 连等赋值问题
  2. 为什么多对多关系需要建立中间表_中间表是什么?和报表有什么关系?会带来怎样的问题?又如何解决?...
  3. 提取地图中道路_逼疯谷歌地球!我用神器一键提取各种肌理,还能生成3D模型!...
  4. 前端学习(495):嵌入代码与外部文件和文档模式
  5. oracle 10g中没有refhost.xml,解决win7 安装oracle10g的问题
  6. 一直在构建工作空间_智能工作空间让Dropbox拥有无限扩展潜力
  7. Nodejs学习笔记(七)——接口API
  8. JS中对于prototype的理解
  9. 不依赖jstack的java 线程dump和死锁检查工具
  10. Android sdk下载安装配置教程
  11. QTP下载地址及破解方法
  12. mcgs组态软件中字体如果从左到右变化_MCGS全中文组态软件常见问题
  13. X-VECTORS: ROBUST DNN EMBEDDINGS FOR SPEAKER RECOGNITION论文翻译
  14. Ubuntu18.04 使用gnome-tweak美化系统主题
  15. 分数的大小比较优秀教案_分数的大小比较 教学设计
  16. C++描述 104.仓库选址
  17. Android源码学习------SystemUI(二)
  18. 香蕉派 BPI-P2 Zero 四核开源物联网开发板,支持PoE网络供电
  19. 支付宝小程序动态绑定样式
  20. TI杯 LaunchPad MSP430开发环境搭建

热门文章

  1. Version of Delve is too old for this version of Go【Goland Debug】报错
  2. 最大的矩形问题(201312-3)
  3. 英美概况复习(内容完善中)
  4. IM服务器检测客户端心跳
  5. 2014创新工场校园招聘笔试题(9.16北京)
  6. ROS 公用包学习解析 usb_cam
  7. 圆柱面与球面相交的matlab,机械制图常识:圆柱与球面相贯
  8. 经济学中ppf计算机会成本例题,微观经济学阶段测试试题1-3
  9. 【好数推荐】方言语音数据集
  10. 服务器硬盘RAID知识