Android如何使用RecyclerView打造首页轮播图
先看看效果图:
停在中间自动翻页
序言:最近接到一个任务,做一个类似上面自动翻页的功能。可以看到,这一屏中有三张图片显示出来了,有两张没有显示完全,看到设计图的时候第一反应是可以用viewpager来实现,但是任务却跟你开了一个天大的玩笑,要求是以最左边的图片为基准,也就是说,左边的图片也得显示完全,就像下图所示,后来仔细想想viewpager好像没有这样的功能,也有可能是我不知道,我也没有找到这样的文章或者信息,希望知道的简友私戳交流一下,感激不尽,好了,言归正传
停在左边
在开始之前呢,首先介绍一个google最新(其实在24.2.0版本的时候就已经发布了)发布的一个东西snaphelper,这玩意儿是对recyclerview功能的一个拓展,有兴趣的同学可以去看看它的源码,snaphelper的实现原理是监听recyclerview.onflinglistener中的onfling接口,可以使recyclerview实现类似viewpager的功能,无论怎么滑动最终停留在某页正中间,那它和viewpager的区别是什么呢?就是viewpager不能一次连续滑动多张图片,而且不能定制(停在左边,还是停在右边)。下面我们一起来看看吧!
首先导入所需要的包,最低版本是v7-24.2.0,低了就没有这个类了:
compile 'com.android.support:appcompat-v7:24.2.0'
compile 'com.android.support:recyclerview-v7:24.2.0'
这里系统自带有一个类linearsnaphelper,linearsnaphelper继承自snaphelper,这个默认是让视图停在中间的,你只需要将recyclerview和linearsnaphelper绑定在一起就行了:
linearsnaphelper mlinearsnaphelper = new linearsnaphelper();
mlinearsnaphelper.attachtorecyclerview(mrecyclerview);
效果如下:
当然了,snaphelper的功能绝不仅仅在此,你还可以定制化,让他停在左边,或者右边,而你不需要重新继承snaphelper,直接继承linearsnaphelper就可以了,这里面有很多写好的方法,然后你再重写里面的两个方法:
* (1)、calculatedistancetofinalsnap:当拖拽或滑动结束时会回调该方法,返回一个out = int[2],out[0]x轴,out[1] y轴 ,这个值就是需要修正的你需要的位置的偏移量。 *
* (2)、findsnapview:这个方法用来获取特定的视图,当返回null时,表示没有获取到任何视图 。*
完整的代码:
public class leftsnaphelper extends linearsnaphelper { private orientationhelper mhorizontalhelper; /** * 当拖拽或滑动结束时会回调该方法,该方法返回的是一个长度为2的数组,out[0]表示横轴,x[1]表示纵轴,这两个值就是你需要修正的位置的偏移量 * * @param layoutmanager * @param targetview * @return */ @override public int[] calculatedistancetofinalsnap(recyclerview.layoutmanager layoutmanager, view targetview) { //注:由于是横向滚动,在这里我们只考虑横轴的值 int[] out = new int[2]; if (layoutmanager.canscrollhorizontally()) { out[0] = distancetostart(targetview, gethorizontalhelper(layoutmanager)); } else { out[0] = 0; } return out; } /** * 这个方法是计算偏移量 * * @param targetview * @param helper * @return */ private int distancetostart(view targetview, orientationhelper helper) { return helper.getdecoratedstart(targetview) - helper.getstartafterpadding(); } @override public view findsnapview(recyclerview.layoutmanager layoutmanager) { return findstartview(layoutmanager, gethorizontalhelper(layoutmanager)); } /** * 找到第一个显示的view * @param layoutmanager * @param helper * @return */ private view findstartview(recyclerview.layoutmanager layoutmanager, orientationhelper helper) { if (layoutmanager instanceof linearlayoutmanager) { int firstchild = ((linearlayoutmanager) layoutmanager).findfirstvisibleitemposition(); int lastchild = ((linearlayoutmanager) layoutmanager).findlastvisibleitemposition(); if (firstchild == recyclerview.no_position) { return null; } //这是为了解决当翻到最后一页的时候,最后一个item不能完整显示的问题 if (lastchild == layoutmanager.getitemcount() - 1) { return layoutmanager.findviewbyposition(lastchild); } view child = layoutmanager.findviewbyposition(firstchild); //得到此时需要左对齐显示的条目 if (helper.getdecoratedend(child) >= helper.getdecoratedmeasurement(child) / 2 && helper.getdecoratedend(child) > 0) { return child; } else { return layoutmanager.findviewbyposition(firstchild + 1); } } return super.findsnapview(layoutmanager); } /** * 获取视图的方向 * * @param layoutmanager * @return */ private orientationhelper gethorizontalhelper(@nonnull recyclerview.layoutmanager layoutmanager) { if (mhorizontalhelper == null) { mhorizontalhelper = orientationhelper.createhorizontalhelper(layoutmanager); } return mhorizontalhelper; } }
当然了,你也可以让它停在右边:只需要在上面的基础上修改findsnapview方法即可:
public class rightsnaphelper extends linearsnaphelper { private orientationhelper mhorizontalhelper; /** * 当拖拽或滑动结束时会回调该方法,该方法返回的是一个长度为2的数组,out[0]表示横轴,x[1]表示纵轴,这两个值就是你需要修正的位置的偏移量 * * @param layoutmanager * @param targetview * @return */ @override public int[] calculatedistancetofinalsnap(recyclerview.layoutmanager layoutmanager, view targetview) { //注:由于是横向滚动,在这里我们只考虑横轴的值 int[] out = new int[2]; if (layoutmanager.canscrollhorizontally()) { out[0] = distancetoend(targetview, gethorizontalhelper(layoutmanager)); } else { out[0] = 0; } return out; } /** * 这个方法是计算偏移量 * * @param targetview * @param helper * @return */ private int distancetoend(view targetview, orientationhelper helper) { return helper.getdecoratedend(targetview) - helper.getendafterpadding(); } @override public view findsnapview(recyclerview.layoutmanager layoutmanager) { return findendview(layoutmanager, gethorizontalhelper(layoutmanager)); } /** * 找到第一个显示的view * * @param layoutmanager * @param helper * @return */ private view findendview(recyclerview.layoutmanager layoutmanager, orientationhelper helper) { if (layoutmanager instanceof linearlayoutmanager) { int lastchild = ((linearlayoutmanager) layoutmanager).findlastvisibleitemposition(); if (lastchild == recyclerview.no_position) { return null; } view child = layoutmanager.findviewbyposition(lastchild); //得到此时需要右对齐显示的条目 if (helper.getdecoratedstart(child) >= helper.getdecoratedmeasurement(child) / 2 && helper.getdecoratedstart(child) > 0) { return child; } else { return layoutmanager.findviewbyposition(lastchild - 1); } } return super.findsnapview(layoutmanager); } /** * 获取视图的方向 * * @param layoutmanager * @return */ private orientationhelper gethorizontalhelper(@nonnull recyclerview.layoutmanager layoutmanager) { if (mhorizontalhelper == null) { mhorizontalhelper = orientationhelper.createhorizontalhelper(layoutmanager); } return mhorizontalhelper; } }
效果:
停在右边
那如何让它能无限的滑动呢?
这个当然是要在adapter里面“做手脚”了,让获取item总数的方法返回integer.max_value就可以了:
@override public int getitemcount() { return integer.max_value; }
然后在onbindviewholder中获取list中的值时相应的取余就好了:
@override public void onbindviewholder(recyclerviewholder holder, int position) { glide.with(mcontext).load(mlist.get(position % mlist.size()) .getimageurl()).placeholder(r.mipmap.ic_launcher) .into(holder.ivimage); holder.tvname.settext(mlist.get(position % mlist.size()).getname()); }
好了,做到这里就完成了80%了,接下来我们要让它能够自动滚动,如何能自动滚动呢?这里可以参考一下viewpager中自动滚动的效果,这里lz使用的是timer来实现,timer有一个每隔多长时间执行一次的功能,在这里正好:
private int cnt = 2; //表示当前最右边显示的item的position private boolean isslidingbyhand = false; //表示是否是手在滑动 private boolean isslidingauto = true; //表示是否自动滑动 timer.schedule(new timertask() { @override public void run() { if (isslidingauto) { myhandler.sendemptymessage(change_item); } } }, 1000, 3000);
考虑到这里有两种滑动,一种是用户手动的滑动,另一种是我们的timer来出发滑动,我们还得对recyclerview设置监听,来简单判断一下是用户触发的还是timer触发的:
alrecyclerview.addonscrolllistener(new recyclerview.onscrolllistener() { @override public void onscrollstatechanged(recyclerview recyclerview, int newstate) { linearlayoutmanager manager = (linearlayoutmanager) recyclerview.getlayoutmanager(); int firstvisibleitemposition = manager.findfirstvisibleitemposition(); switch (newstate) { case scroll_state_idle: //(静止没有滚动) if (isslidingbyhand) { message msg = myhandler.obtainmessage(); msg.arg1 = firstvisibleitemposition; msg.what = change_item; myhandler.sendmessage(msg); } break; case scroll_state_dragging: //(正在被外部拖拽,一般为用户正在用手指滚动) isslidingbyhand = true; isslidingauto = false; break; case scroll_state_settling: //(自动滚动) if (isslidingbyhand) { isslidingauto = false; } else { isslidingauto = true; } break; } } });
最后的处理结果当然是交给handler来了:
private static class myhandler extends handler { //采用弱引用的方式,防止内存泄漏 weakreference<centeractivity> weakreference; public myhandler(centeractivity mactivity) { this.weakreference = new weakreference<>(mactivity); } @override public void handlemessage(message msg) { final centeractivity mactivity = weakreference.get(); log.d(tag, "handlemessage: " + "handler is running"); if (mactivity.isslidingbyhand) { mactivity.cnt = msg.arg1; mactivity.isslidingbyhand = false; mactivity.isslidingauto = true; mactivity.cnt+=2; //让recyclerview平滑的滚动 mactivity.alrecyclerview.smoothscrolltoposition(++mactivity.cnt); } else { mactivity.alrecyclerview.smoothscrolltoposition(++mactivity.cnt); } } }
这样差不多就好了,但是还有一个问题不知道各位有没有关注到,滚动的时候并没有那么平滑,找了好久不知道是什么原因,希望知道的朋友底下评论告知一声。
参考文章:
orientationhelper api
android24.2.0支持库中的snaphelper学习和使用
感谢以上资料的帮助。
源码我已上传至github,想了解的朋友可以自行下载,当然,如果有更好的想法,也可以一起交流。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。