3.2.1 一篇文章完全掌握 RecycleView 的六大用法
点此进入:从零快速构建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 的六大用法相关推荐
- 一篇文章告诉你标准化和归一化的区别?
一篇文章告诉你标准化和归一化的区别? 2019-02-28 17:12:39 融融网融融网阅读量:484 进一步推进企业的标准化工作,使之发展水平适应经济全球化下市场竞争的要求,促进企业综合实力的提升 ...
- 一篇文章让你读懂Pivotal的GemFire家族产品
一篇文章让你读懂Pivotal的GemFire家族产品 学习了:https://www.sohu.com/a/217157517_747818 转载于:https://www.cnblogs.com/ ...
- DEDECMS教程:上/下一篇文章标题长度的截取方法
对dedecms了解的朋友们,想必对如何获取上一篇.下一篇文章的标签也是非常熟悉.dedecms获取上一篇.下一篇文章的标签分别为:{dede:prenext get='pre'/}.{dede:pr ...
- 一篇文章一张思维导图看懂Android学习最佳路线
一篇文章一张思维导图看懂Android学习最佳路线 先上一张android开发知识点学习路线图思维导图 Android学习路线从4个阶段来对Android的学习过程做一个全面的分析:Android初级 ...
- UML科普文,一篇文章掌握14种UML图
前言 上一篇文章写了一篇建造者模式,其中有几个UML类图,有的读者反馈看不懂了,我们今天就来解决一哈. 什么是UML? UML是Unified Model Language的缩写,中文是统一建模语言, ...
- 用一篇文章说清楚如何写作
专门讲写作的书就有一大堆,这事能用一篇文章说清楚吗? 答案是能的,不信你往下看. 写之前要先弄清楚文章属于什么类型,类型不同写法当然不一样.以沟通为目的的文章最好写,虚构类文章不好写,因为你还要先虚构 ...
- 一篇文章告诉你如何成为数据科学家
文章讲的是一篇文章告诉你如何成为数据科学家,通常来说,年轻人都很容易立志成为什么,例如成为一名科学家,然后又很快放弃.这一方面是因为摆在他们面前的诱惑太多,也因为成为一名科学家真的很不容易. 这一点放 ...
- Android:学习AIDL,这一篇文章就够了(下)
前言 上一篇博文介绍了关于AIDL是什么,为什么我们需要AIDL,AIDL的语法以及如何使用AIDL等方面的知识,这一篇博文将顺着上一篇的思路往下走,接着介绍关于AIDL的一些更加深入的知识.强烈建议 ...
- Android:学习AIDL,这一篇文章就够了(上)
前言 在决定用这个标题之前甚是忐忑,主要是担心自己对AIDL的理解不够深入,到时候大家看了之后说--你这是什么玩意儿,就这么点东西就敢说够了?简直是坐井观天不知所谓--那样就很尴尬了.不过又转念一想, ...
最新文章
- python能不能连等赋值_javascript 连等赋值问题
- 为什么多对多关系需要建立中间表_中间表是什么?和报表有什么关系?会带来怎样的问题?又如何解决?...
- 提取地图中道路_逼疯谷歌地球!我用神器一键提取各种肌理,还能生成3D模型!...
- 前端学习(495):嵌入代码与外部文件和文档模式
- oracle 10g中没有refhost.xml,解决win7 安装oracle10g的问题
- 一直在构建工作空间_智能工作空间让Dropbox拥有无限扩展潜力
- Nodejs学习笔记(七)——接口API
- JS中对于prototype的理解
- 不依赖jstack的java 线程dump和死锁检查工具
- Android sdk下载安装配置教程
- QTP下载地址及破解方法
- mcgs组态软件中字体如果从左到右变化_MCGS全中文组态软件常见问题
- X-VECTORS: ROBUST DNN EMBEDDINGS FOR SPEAKER RECOGNITION论文翻译
- Ubuntu18.04 使用gnome-tweak美化系统主题
- 分数的大小比较优秀教案_分数的大小比较 教学设计
- C++描述 104.仓库选址
- Android源码学习------SystemUI(二)
- 香蕉派 BPI-P2 Zero 四核开源物联网开发板,支持PoE网络供电
- 支付宝小程序动态绑定样式
- TI杯 LaunchPad MSP430开发环境搭建