Android RecyclerView实现下拉刷新和上拉加载
recyclerview已经出来很久了,许许多多的项目都开始从listview转战recyclerview,那么,上拉加载和下拉刷新是一件很有必要的事情。
在listview上,我们可以通过自己添加addheadview和addfootview去添加头布局和底部局实现自定义的上拉和下拉,或者使用一些第三方库来简单的集成,例如android-pulltorefresh或者android-ultra-pull-to-refresh,后者的自定义更强,但需要自己实现上拉加载。
而在下面我们将用两种方式来实现上拉加载和下拉刷新
第一种方式:swiperefreshlayout+滑动底部自动加载
第二种方式:使用第三方库swipetoloadlayout实现上拉加载和下拉刷新。
第一种方式:swiperefreshlayout+滑动底部自动加载
swiperefreshlayout实现很简单,重点是滑动到底部自动加载应该如何实现,其实其实现的方式类似于listview的实现方式。
看一下activity_recycle_swiperefresh.xml文件:
<android.support.v4.widget.swiperefreshlayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/swipe_refresh" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.recyclerview android:id="@+id/swipe_target" android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none" /> </android.support.v4.widget.swiperefreshlayout>
布局文件就两个控件,swiperefreshlayout中嵌套recyclerview。
在代码中初始化recyclerview以及实现adapter等,这不是重点,不再贴代码。
在recyclerview中有方法addonscrolllistener,该方法类似于listview的setonscrolllistener方法,onscrolllistener中有两个方法的回调
*onscrolled(recyclerview recyclerview, int dx, int dy) :滚动的回调,dx和dy表示手指滑动水平和垂直的偏移量。
*onscrollstatechanged(recyclerview recyclerview, int newstate):滑动状态的回调。
那么,我们的着重点就在这个两个方法上了。
对于向上加载更多,我们需要有如下判断
--是否是向上滑动
--是否滑动到底部
--当前是否正在加载数据
--当前状态是否是滑动停止的状态
实现比较复杂,定义一个类loaddatascrollcontroller,继承类recyclerview.onscrolllistener,
因为onscrollstatechanged实在状态改变时的回调,无法时时的获取显示的条目以及位置,所以我们在onscrolled中获取相应位置,
@override public void onscrolled(recyclerview recyclerview, int dx, int dy) { /** * 获取布局参数 */ recyclerview.layoutmanager layoutmanager = recyclerview.getlayoutmanager(); //如果为null,第一次运行,确定布局类型 if (mlayoutmanagertype == null) { if (layoutmanager instanceof linearlayoutmanager) { mlayoutmanagertype = layoutmanagertype.linear_layout; } else if (layoutmanager instanceof gridlayoutmanager) { mlayoutmanagertype = layoutmanagertype.grid_layout; } else if (layoutmanager instanceof staggeredgridlayoutmanager) { mlayoutmanagertype = layoutmanagertype.staggered_grid_layout; } else { throw new runtimeexception("layoutmanager should be linearlayoutmanager,gridlayoutmanager,staggeredgridlayoutmanager"); } } //对于不太能够的布局参数,不同的方法获取到当前显示的最后一个条目数 switch (mlayoutmanagertype) { case linear_layout: mlastvisibleitemposition = ((linearlayoutmanager) layoutmanager).findlastvisibleitemposition(); break; case grid_layout: mlastvisibleitemposition = ((gridlayoutmanager) layoutmanager).findlastvisibleitemposition(); break; case staggered_grid_layout: staggeredgridlayoutmanager staggeredgridlayoutmanager = (staggeredgridlayoutmanager) layoutmanager; if (mlastpostions == null) { mlastpostions = new int[staggeredgridlayoutmanager.getspancount()]; } staggeredgridlayoutmanager.findlastvisibleitempositions(mlastpostions); mlastvisibleitemposition = findmax(mlastpostions); break; default: break; } }
首先获取布局管理器,并判断是那种类型的,因为有三种类型,定义枚举来保存布局类型的参数
/** * * recycleview的布局管理器的类型 * created by alex_mahao on 2016/5/10. */ public enum layoutmanagertype { linear_layout, grid_layout, staggered_grid_layout }
然后根据布局惯例其的类型获取其当前显示的最大条目,对于瀑布流来说,他如果是垂直的两列瀑布的话,我们需要获取两列中分别最大条目数,进行比较,选出最大条目数。
/** * 当是瀑布流时,获取到的是每一个瀑布最下方显示的条目,通过条目进行对比 */ private int findmax(int[] lastpositions) { int max = lastpositions[0]; for (int value : lastpositions) { if (value > max) { max = value; } } return max; }
拿到当前最大的条目数之后,在onscrollstatechange中进行判断状态等,
@override public void onscrollstatechanged(recyclerview recyclerview, int newstate) { recyclerview.layoutmanager layoutmanager = recyclerview.getlayoutmanager(); //recycleview 显示的条目数 int visiblecount = layoutmanager.getchildcount(); //显示数据总数 int totalcount = layoutmanager.getitemcount(); // 四个条件,分别是是否有数据,状态是否是滑动停止状态,显示的最大条目是否大于整个数据(注意偏移量),是否正在加载数据 if(visiblecount>0 &&newstate==recyclerview.scroll_state_idle &&mlastvisibleitemposition>=totalcount-1 &&!isloaddata){ //可以加载数据 isloaddata = true; } }
注释很清楚,在加载数据的地方,我们将isloaddata设为true,同时利用接口回调加载数据,等数据加载完成,通过setloaddatastatus方法设置为false
public void setloaddatastatus(boolean isloaddata){ this.isloaddata = isloaddata; }
如果这样就结束了,感觉很麻烦,对于刷新和加载更多,我们需要在调用的地方分别设置监听,那么我们可以让loaddatascrollcontroller实现swiperefreshlayout的刷新监听方法,在利用我们定义的统一的上拉刷新和加载数据接口进行处理
/** * 实现上拉加载的监听:加载条件:滑动到最后,且是停止状态,则开始加载数据 * created by alex_mahao on 2016/5/10. */ public class loaddatascrollcontroller extends recyclerview.onscrolllistener implements swiperefreshlayout.onrefreshlistener { /** * 当前布局管理器的类型 */ private layoutmanagertype mlayoutmanagertype; /** * 当前recycleview显示的最大条目 */ private int mlastvisibleitemposition; /** * 每列的最后一个条目 */ private int[] mlastpostions; /** * 是否正在加载数据 包括刷新和向上加载更多 */ private boolean isloaddata = false; /** * 回调接口 */ private onrecyclerefreshlistener mlistener; public loaddatascrollcontroller(onrecyclerefreshlistener onrecyclerefreshlistener) { this.mlistener = onrecyclerefreshlistener; } @override public void onscrolled(recyclerview recyclerview, int dx, int dy) { /** * 获取布局参数 */ recyclerview.layoutmanager layoutmanager = recyclerview.getlayoutmanager(); //如果为null,第一次运行,确定布局类型 if (mlayoutmanagertype == null) { if (layoutmanager instanceof linearlayoutmanager) { mlayoutmanagertype = layoutmanagertype.linear_layout; } else if (layoutmanager instanceof gridlayoutmanager) { mlayoutmanagertype = layoutmanagertype.grid_layout; } else if (layoutmanager instanceof staggeredgridlayoutmanager) { mlayoutmanagertype = layoutmanagertype.staggered_grid_layout; } else { throw new runtimeexception("layoutmanager should be linearlayoutmanager,gridlayoutmanager,staggeredgridlayoutmanager"); } } //对于不太能够的布局参数,不同的方法获取到当前显示的最后一个条目数 switch (mlayoutmanagertype) { case linear_layout: mlastvisibleitemposition = ((linearlayoutmanager) layoutmanager).findlastvisibleitemposition(); break; case grid_layout: mlastvisibleitemposition = ((gridlayoutmanager) layoutmanager).findlastvisibleitemposition(); break; case staggered_grid_layout: staggeredgridlayoutmanager staggeredgridlayoutmanager = (staggeredgridlayoutmanager) layoutmanager; if (mlastpostions == null) { mlastpostions = new int[staggeredgridlayoutmanager.getspancount()]; } staggeredgridlayoutmanager.findlastvisibleitempositions(mlastpostions); mlastvisibleitemposition = findmax(mlastpostions); break; default: break; } } @override public void onscrollstatechanged(recyclerview recyclerview, int newstate) { recyclerview.layoutmanager layoutmanager = recyclerview.getlayoutmanager(); //recycleview 显示的条目数 int visiblecount = layoutmanager.getchildcount(); //显示数据总数 int totalcount = layoutmanager.getitemcount(); // 四个条件,分别是是否有数据,状态是否是滑动停止状态,显示的最大条目是否大于整个数据(注意偏移量),是否正在加载数据 if(visiblecount>0 &&newstate==recyclerview.scroll_state_idle &&mlastvisibleitemposition>=totalcount-1 &&!isloaddata){ //可以加载数据 if(mlistener!=null){ isloaddata = true; mlistener.loadmore(); } } } /** * 当是瀑布流时,获取到的是每一个瀑布最下方显示的条目,通过条目进行对比 */ private int findmax(int[] lastpositions) { int max = lastpositions[0]; for (int value : lastpositions) { if (value > max) { max = value; } } return max; } public void setloaddatastatus(boolean isloaddata){ this.isloaddata = isloaddata; } @override public void onrefresh() { //刷新数据的方法 if(mlistener!=null){ isloaddata = true; mlistener.refresh(); } } /** * 数据加载接口回调 */ interface onrecyclerefreshlistener{ void refresh(); void loadmore(); } }
最后看一下main的代码
/** * 使用原生的swiperefreshlayout和代码判断 * 实现recyclewview 的刷新和加载更多 * * created by alex_mahao on 2016/5/10. */ public class swiperefreshactivity extends appcompatactivity implements loaddatascrollcontroller.onrecyclerefreshlistener { private swiperefreshlayout mswiperefresh; private recyclerview mrecycle; private homeadapter madapter; private loaddatascrollcontroller mcontroller; private progressdialog pd; @override protected void oncreate(@nullable bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_recycle_swiperefresh); mrecycle = ((recyclerview) findviewbyid(r.id.swipe_target)); mswiperefresh = ((swiperefreshlayout) findviewbyid(r.id.swipe_refresh)); mswiperefresh.setcolorschemecolors(color.red,color.green,color.blue); /** * 创建控制器,同时使当前activity实现数据监听回调接口 */ mcontroller = new loaddatascrollcontroller(this); madapter = new homeadapter(); //设置垂直的线性布局管理器,orientation --> vertical:垂直 horizontal:水平 linearlayoutmanager layoutmanager = new linearlayoutmanager(this); layoutmanager.setorientation(linearlayoutmanager.vertical); //staggeredgridlayoutmanager layoutmanager = new staggeredgridlayoutmanager(2,staggeredgridlayoutmanager.vertical); //添加分割线 mrecycle.additemdecoration(new divideritemdecoration(getapplicationcontext(), divideritemdecoration.vertical_list)); mrecycle.setlayoutmanager(layoutmanager); mrecycle.setitemanimator(new defaultitemanimator()); mrecycle.setadapter(madapter); madapter.refresh(); /** * 设置监听 */ mrecycle.addonscrolllistener(mcontroller); mswiperefresh.setonrefreshlistener(mcontroller); } @override public void refresh() { //刷新的接口调 mswiperefresh.postdelayed(new runnable() { @override public void run() { madapter.refresh(); mswiperefresh.setrefreshing(false); mcontroller.setloaddatastatus(false); } },2000); } @override public void loadmore() { //加载更多的接口回调 pd = new progressdialog(this); pd.show(); mswiperefresh.postdelayed(new runnable() { @override public void run() { madapter.add(); //设置数据加载结束的监听状态 mcontroller.setloaddatastatus(false); pd.dismiss(); } },2000); } }
贴个效果图
第二种方式:swipetoloadlayout实现上拉加载和下拉刷新
该刷新控件的方式类似于ultra-pull-to-refresh的使用方式。
如下方式添加该库:
repositories { maven { url "https://jitpack.io" } } compile 'com.github.aspsine:swipetoloadlayout:1.0.3'
首先我们需要自定义一个头视图和底部视图,头部试图和底部试图的用法相同,所以我们先定义一个头部视图类:
/** * 基础的refreshheadview */ public class refreshheaderview extends textview implements swiperefreshtrigger, swipetrigger { public refreshheaderview(context context) { super(context); } public refreshheaderview(context context, attributeset attrs) { super(context, attrs); } public refreshheaderview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); } @targetapi(build.version_codes.lollipop) public refreshheaderview(context context, attributeset attrs, int defstyleattr, int defstyleres) { super(context, attrs, defstyleattr, defstyleres); } @override public void onrefresh() { //下拉到一定位置松开之后,调用此方法 settext("refresh"); log.i("info","onrefresh"); } @override public void onprepare() { //下拉之前调用此方法 log.i("info","onprepare"); } @override public void onmove(int yscrolled, boolean iscomplete, boolean automatic) { if (!iscomplete) { //当前y轴偏移量大于控件高度时,标识下拉到界限,显示“松开已刷新” if (yscrolled >= getheight()) { } else { //未达到偏移量 } } log.i("info","onmove"); } @override public void onrelease() { //达到一定滑动距离,松开刷新时调用 settext("onrelease"); log.i("info","onrelease"); } @override public void oncomplete() { //加载完成之后调用此方法 settext("complete"); log.i("info","oncomplete"); } @override public void onreset() { //重置 settext("onreset"); log.i("info","onreset"); } }
其需要实现接口swiperefreshtrigger和swipetrigger。
而底部需要实现swipetrigger和swipeloadmoretrigger。
布局文件中如下使用
<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" android:background="#ecedf0" > <com.aspsine.swipetoloadlayout.swipetoloadlayout android:id="@+id/swipetoloadlayout" android:layout_width="match_parent" android:layout_height="match_parent"> <com.mahao.alex.systemwidgetdemo.recycleview.swipetoloadlayout.refreshheaderview android:id="@+id/swipe_refresh_header" android:layout_width="match_parent" android:layout_height="wrap_content" /> <android.support.v7.widget.recyclerview android:id="@+id/swipe_target" android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="vertical" /> <com.mahao.alex.systemwidgetdemo.recycleview.swipetoloadlayout.loadermoreview android:id="@+id/swipe_load_more_footer" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="20dp" /> </com.aspsine.swipetoloadlayout.swipetoloadlayout> </relativelayout>
查找控件,设置监听
swipetoloadlayout.setonrefreshlistener(this);
swipetoloadlayout.setonloadmorelistener(this);
在我们之前的代码中,加入了log信息,我们可以看一下log信息。…代表多次onmove()方法多次调用。
05-10 10:30:34.396 23814-23814/com.mahao.alex.systemwidgetdemo i/info: onprepare
05-10 10:30:34.536 23814-23814/com.mahao.alex.systemwidgetdemo i/info: onmove
..........................................................................
05-10 10:30:34.886 23814-23814/com.mahao.alex.systemwidgetdemo i/info: onmove
05-10 10:30:34.896 23814-23814/com.mahao.alex.systemwidgetdemo i/info: onrelease
05-10 10:30:34.906 23814-23814/com.mahao.alex.systemwidgetdemo i/info: onmove
..........................................................................
05-10 10:30:35.086 23814-23814/com.mahao.alex.systemwidgetdemo i/info: onmove
05-10 10:30:35.106 23814-23814/com.mahao.alex.systemwidgetdemo i/info: onrefresh
05-10 10:30:37.116 23814-23814/com.mahao.alex.systemwidgetdemo i/info: oncomplete
05-10 10:30:37.416 23814-23814/com.mahao.alex.systemwidgetdemo i/info: onmove
..........................................................................
05-10 10:30:37.516 23814-23814/com.mahao.alex.systemwidgetdemo i/info: onmove
05-10 10:30:37.916 23814-23814/com.mahao.alex.systemwidgetdemo i/info: onreset
首先会调用onprepare()方法,onmove()方法会一直调用,只要视图有偏移,就会调用。下拉到一定距离之后,松开调用onrelaease(),回归到刷新位置时回调onrefresh(),加载完成调用oncomplete(),视图开始缩小,最后隐藏之后调用onreset()
根据需求自定义视图,
定义我们的椭圆,使用自定义控件
/** * circleview 圆盘控件,可以旋转 * created by alex_mahao on 2016/5/10. */ public class circleview extends view { /** * 控件的半径 */ private int mradius; /** * 绘制弧形的画笔 */ private paint marcpaint; /** * 绘制弧形的区域 */ private rectf mrange; private int[] colors = {color.red, color.blue, color.yellow, color.green}; public circleview(context context) { this(context, null, 0); } public circleview(context context, attributeset attrs) { this(context, attrs, 0); } public circleview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); init(); } private void init() { marcpaint = new paint(); marcpaint.setantialias(true); marcpaint.setdither(true); marcpaint.setstyle(paint.style.fill); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); int width = 0; int height = 0; int widthsize = measurespec.getsize(widthmeasurespec); int widthmode = measurespec.getmode(widthmeasurespec); int heightsize = measurespec.getsize(heightmeasurespec); int heightmode = measurespec.getmode(heightmeasurespec); if (widthmode == measurespec.exactly) { width = widthsize; } else { width = (int) typedvalue.applydimension(typedvalue.complex_unit_dip, 45, getresources().getdisplaymetrics()); } if (heightmode == measurespec.exactly) { height = heightsize; } else { height = (int) typedvalue.applydimension(typedvalue.complex_unit_dip, 45, getresources().getdisplaymetrics()); } //获取半径 mradius = math.min(width, height) / 2; /** * 设置宽高为固定值 */ setmeasureddimension(mradius * 2, mradius * 2); mrange = new rectf(0, 0, mradius * 2, mradius * 2); } @override protected void ondraw(canvas canvas) { float degree = 360/colors.length/2f; for (int i = 0; i < 8; i++) { marcpaint.setcolor(colors[i%4]); canvas.drawarc(mrange,-90f+degree*i,degree,true,marcpaint); } } }
绘制头部刷新试图
** * 自定义的下拉刷新控件 头部 * created by alex_mahao on 2016/5/10. */ public class circlerefreshheaderview extends relativelayout implements swipetrigger, swiperefreshtrigger { circleview mcircleview; textview mdesctext; private objectanimator anim; private boolean isrelease; public circlerefreshheaderview(context context) { this(context, null, 0); } public circlerefreshheaderview(context context, attributeset attrs) { this(context, attrs, 0); } public circlerefreshheaderview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); initview(); } /** * 初始化布局 */ private void initview() { int circlewidth = (int) typedvalue.applydimension(typedvalue.complex_unit_dip, 30, getresources().getdisplaymetrics()); mcircleview = new circleview(getcontext()); linearlayout.layoutparams circleparams = new linearlayout.layoutparams(circlewidth,circlewidth); mcircleview.setlayoutparams(circleparams); mdesctext = new textview(getcontext()); linearlayout.layoutparams descparams = new linearlayout.layoutparams(circlewidth*3, viewgroup.layoutparams.wrap_content); descparams.gravity = gravity.center; descparams.setmargins(circlewidth/2,0,0,0); mdesctext.setlayoutparams(descparams); mdesctext.settextsize(12); mdesctext.settextcolor(color.gray); mdesctext.settext("下拉刷新"); //添加线性的父布局 linearlayout ll = new linearlayout(getcontext()); relativelayout.layoutparams llparams = new layoutparams(viewgroup.layoutparams.wrap_content, viewgroup.layoutparams.wrap_content); llparams.addrule(center_in_parent); ll.setlayoutparams(llparams); ll.setpadding(10,10,10,10); ll.addview(mcircleview); ll.addview(mdesctext); addview(ll); } @override public void onrefresh() { //开始刷新,启动动画 anim = objectanimator.offloat(mcircleview, "rotation", mcircleview.getrotation(), mcircleview.getrotation()+360f) .setduration(500); anim.setrepeatcount(valueanimator.infinite); anim.setrepeatmode(valueanimator.restart); anim.start(); mdesctext.settext("正在加载数据"); } @override public void onprepare() { isrelease = false; } @override public void onmove(int yscroll, boolean iscomplete, boolean b1) { if (!iscomplete) { if (yscroll < getheight()) { mdesctext.settext("下拉刷新"); } else { mdesctext.settext("松开刷新更多"); } //如果是仍在下拉状态,则圆环跟随滑动进行滚动 if (!isrelease) mcircleview.setrotation(((float) yscroll) / getheight() * 360f); } } @override public void onrelease() { isrelease = true; } @override public void oncomplete() { anim.cancel(); mdesctext.settext("加载完成"); } @override public void onreset() { //重置时,将动画置为初始状态 mcircleview.setrotation(0f); } }
布局文件
<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" android:background="#ecedf0" > <com.aspsine.swipetoloadlayout.swipetoloadlayout android:id="@+id/swipetoloadlayout" android:layout_width="match_parent" android:layout_height="match_parent"> <com.mahao.alex.systemwidgetdemo.recycleview.swipetoloadlayout.circlerefreshheaderview android:id="@+id/swipe_refresh_header" android:layout_width="match_parent" android:layout_height="wrap_content" /> <android.support.v7.widget.recyclerview android:id="@+id/swipe_target" android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="vertical" /> <com.mahao.alex.systemwidgetdemo.recycleview.swipetoloadlayout.loadermoreview android:id="@+id/swipe_load_more_footer" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="20dp" /> </com.aspsine.swipetoloadlayout.swipetoloadlayout> </relativelayout>
public class swipetolayoutactivity extends appcompatactivity implements onrefreshlistener, onloadmorelistener { private recyclerview mrecycleview; swipetoloadlayout swipetoloadlayout; private homeadapter adapter; @override protected void oncreate(@nullable bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_recycle_swipetolayout); swipetoloadlayout = ((swipetoloadlayout) findviewbyid(r.id.swipetoloadlayout)); mrecycleview = ((recyclerview) findviewbyid(r.id.swipe_target)); adapter = new homeadapter(); //设置垂直的线性布局管理器,orientation --> vertical:垂直 horizontal:水平 linearlayoutmanager layoutmanager = new linearlayoutmanager(this); layoutmanager.setorientation(linearlayoutmanager.vertical); // staggeredgridlayoutmanager layoutmanager = new staggeredgridlayoutmanager(2,staggeredgridlayoutmanager.vertical); //添加分割线 mrecycleview.additemdecoration(new divideritemdecoration(getapplicationcontext(), divideritemdecoration.vertical_list)); mrecycleview.setlayoutmanager(layoutmanager); mrecycleview.setitemanimator(new defaultitemanimator()); mrecycleview.setadapter(adapter); adapter.refresh(); /** * 设置下拉刷新和上拉加载监听 */ swipetoloadlayout.setonrefreshlistener(this); swipetoloadlayout.setonloadmorelistener(this); } @override public void onrefresh() { swipetoloadlayout.postdelayed(new runnable() { @override public void run() { adapter.refresh(); swipetoloadlayout.setrefreshing(false); } },2000); } @override public void onloadmore() { swipetoloadlayout.postdelayed(new runnable() { @override public void run() { adapter.add(); swipetoloadlayout.setloadingmore(false); } },2000); } }
ok。肯定有小伙伴使用该框架时一直报错,为什么,看框架的源码,有如下一段
this.mheaderview = this.findviewbyid(id.swipe_refresh_header); this.mtargetview = this.findviewbyid(id.swipe_target); this.mfooterview = this.findviewbyid(id.swipe_load_more_footer);
可以看出,作者是根据固定的id值获取的,所以在我们的布局文件中,必须使用固定的三个id。
如有需求,可移步我的github获取源码,源码在systemwidgetdemo中。
以上就是本文的全部内容,希望对大家学习android软件编程有所帮助。
上一篇: python爬虫headers设置后无效的解决方法
下一篇: Java 线程死锁的问题解决办法
推荐阅读
-
Android RecyclerView实现下拉刷新和上拉加载
-
Android ListView实现上拉加载更多和下拉刷新功能
-
Android使用recyclerview打造真正的下拉刷新上拉加载效果
-
GridView基于pulltorefresh实现下拉刷新 上拉加载更多功能(推荐)
-
Android XListView下拉刷新和上拉加载更多
-
Android自定义下拉刷新上拉加载
-
Android UI自定义ListView实现下拉刷新和加载更多效果
-
Android RecyclerView实现下拉刷新和上拉加载
-
Android ListView实现上拉加载更多和下拉刷新功能
-
GridView基于pulltorefresh实现下拉刷新 上拉加载更多功能(推荐)