3.2.1 一篇文章完全掌握 RecycleView 的六大用法
2017-09-22 11:19
766 查看
点此进入:从零快速构建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.RecyclerView android: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所调用 @Override public 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; } // 将数据与界面进行绑定的操作 @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { viewHolder.mTextView.setText(datas[position]); } // 获取数据的数量 @Override public 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{ @Override public 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; } @Override public void onBindViewHolder(ViewHolder viewHolder, final int i) { viewHolder.mTextView.setText(datas.get(i).title); // 将数据保存在itemView的Tag中,以便点击时进行获取 viewHolder.itemView.setTag(datas.get(i)); } ... @Override public void onClick(View v) { if (mOnItemClickListener != null) { // 注意这里使用getTag方法获取数据 mOnItemClickListener.onItemClick(v,(DataModel)v.getTag()); } } ... }
当然我们也可以通过Recyclerview直接获取item位置信息:
@Override public 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() { @Override public 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; } @Override public 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); } } @Override public 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() { @Override public 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); } @Override public 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; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { mAdapter.removeItem(viewHolder.getAdapterPosition()); } @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { super.onSelectedChanged(viewHolder, actionState); if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { viewHolder.itemView.setBackgroundColor(Color.LTGRAY); } } @Override public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); viewHolder.itemView.setBackgroundColor(Color.WHITE); } @Override public 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); } @Override public boolean isLongPressDragEnabled() { return true; } @Override public 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(); } @Override public 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; } @Override public 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的强大之处。
联系方式:
简书:WillFlowCSDN:WillFlow
微信公众号:WillFlow
点此进入:GitHub开源项目“爱阅”。“爱阅”专注于收集优质的文章、站点、教程,与大家共分享。下面是“爱阅”的效果图:
相关文章推荐
- Python:一篇文章掌握Numpy的基本用法
- Block声明,定义, 最基础的用法学习,很好的一篇文章
- 究极好的一篇文章,开源项目的用法
- 完全掌握反射的各种用法
- N个任务掌握java系列之统计一篇文章中单词出现的次数
- 一篇文章,掌握所有开源数据库的现状
- 一篇文章就能掌握SPI总线
- Android开发之recycleView详解代码,看完包你熟练掌握recycleView的用法。转自网络经典文章
- 一篇带你完全掌握线程的博客
- 大数据究竟是什么呢?一篇文章让你完全认识和读懂大数据
- 推荐:CLR 完全介绍-一篇讲解CLR内存回收机制以及常见的调试技巧的文章
- 【Networkk】一篇文章完全搞清楚 scoket read/write 返回码、阻塞与非阻塞、异常处理 等让你头疼已久的问题
- 一篇文章,掌握所有开源数据库的现状
- 一篇文章完全搞清楚 scoket read/write 返回码、阻塞与非阻塞、异常处理 等让你头疼已久的问题
- 【线性代数】矩阵、向量、行列式、特征值与特征向量(掌握这些概念一篇文章就够了)
- 学过JDBC才知道前一篇文章的经历完全可以避免。。。
- JS -- 一篇文章掌握RequireJS常用知识
- 一篇文章掌握Sql-On-Hadoop核心技术
- Android自学--一篇文章基本掌握所有的常用View组件
- [轉]完全掌握Smarty的Section用法