Android App开发中使用RecyclerView实现Gallery画廊的实例
什么是recyclerview
recyclerview是android 5.0 materials design中的组件之一,相应的还有cardview、palette等。看名字我们就能看出一点端倪,没错,它主要的特点就是复用。我们知道,listview中的adapter中可以实现viewholder的复用。recyclerview提供了一个耦合度更低的方式来复用viewholder,并且可以轻松的实现listview、gridview以及瀑布流的效果。
recyclerview使用的基本思路
首先我们要gradle的依赖库中添加 compile 'com.android.support:recyclerview-v7:21.+' 。如果是eclipse直接导入android-support-v7-recyclerview.jar就可以了。
/** * 设置adapter */ mrecyclerview.setadapter(mlistadapter); /** * 设置布局管理器 */ mrecyclerview.setlayoutmanager(linearlayoutmanager); /** * 设置item分割线 */ mrecyclerview.additemdecoration(itemdecoration); /** * 设置item动画 */ mrecyclerview.setitemanimator(new defaultitemanimator());
使用recyclerview,基本上要上面四步。相比listview只需设置adapter而言,recyclerview的使用看起来似乎要复杂一些。但是它的可定制性更高了,你可以自己定制自己的分割线样式或者是item的的动画。
实现gallery效果
recyclerview在这里可以被看作为listview的升级版本,下买呢首先介绍recyclerview的用法,然后经行一定的分析;最后自定义一下recyclerview实现我们需要的相册效果。
1、recyclerview的基本用法
首先主activity的布局文件:
<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.support.v7.widget.recyclerview android:id="@+id/id_recyclerview_horizontal" android:layout_width="match_parent" android:layout_height="120dp" android:layout_centervertical="true" android:background="#ff0000" android:scrollbars="none" /> </relativelayout>
item的布局文件:
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="120dp" android:layout_height="120dp" android:background="@drawable/item_bg02" > <imageview android:id="@+id/id_index_gallery_item_image" android:layout_width="80dp" android:layout_height="80dp" android:layout_alignparenttop="true" android:layout_centerhorizontal="true" android:layout_margin="5dp" android:scaletype="centercrop" /> <textview android:id="@+id/id_index_gallery_item_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/id_index_gallery_item_image" android:layout_centerhorizontal="true" android:layout_marginbottom="5dp" android:layout_margintop="5dp" android:textcolor="#ff0000" android:text="some info" android:textsize="12dp" /> </relativelayout>
数据适配器:
package com.example.zhy_horizontalscrollview03; import java.util.list; import android.content.context; import android.support.v7.widget.recyclerview; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; import android.widget.imageview; import android.widget.textview; public class galleryadapter extends recyclerview.adapter<galleryadapter.viewholder> { private layoutinflater minflater; private list<integer> mdatas; public galleryadapter(context context, list<integer> datats) { minflater = layoutinflater.from(context); mdatas = datats; } public static class viewholder extends recyclerview.viewholder { public viewholder(view arg0) { super(arg0); } imageview mimg; textview mtxt; } @override public int getitemcount() { return mdatas.size(); } /** * 创建viewholder */ @override public viewholder oncreateviewholder(viewgroup viewgroup, int i) { view view = minflater.inflate(r.layout.activity_index_gallery_item, viewgroup, false); viewholder viewholder = new viewholder(view); viewholder.mimg = (imageview) view .findviewbyid(r.id.id_index_gallery_item_image); return viewholder; } /** * 设置值 */ @override public void onbindviewholder(final viewholder viewholder, final int i) { viewholder.mimg.setimageresource(mdatas.get(i)); } }
可以看到数据适配器与baseadapter比较发生了相当大的变化,主要有3个方法:
- getitemcount 这个不用说,获取总的条目数
- oncreateviewholder 创建viewholder
- onbindviewholder 将数据绑定至viewholder
可见,recyclerview对viewholder也进行了一定的封装,但是如果你仔细观察,你会发出一个疑问,listview里面有个getview返回view为item的布局,那么这个item的样子在哪控制?
其实是这样的,我们创建的viewholder必须继承recyclerview.viewholder,这个recyclerview.viewholder的构造时必须传入一个view,这个view相当于我们listview getview中的convertview (即:我们需要inflate的item布局需要传入)。
还有一点,listview中convertview是复用的,在recyclerview中,是把viewholder作为缓存的单位了,然后convertview作为viewholder的成员变量保持在viewholder中,也就是说,假设没有屏幕显示10个条目,则会创建10个viewholder缓存起来,每次复用的是viewholder,所以他把getview这个方法变为了oncreateviewholder。有兴趣的自己打印下log,测试下。
最后在activity中使用:
package com.example.zhy_horizontalscrollview03; import java.util.arraylist; import java.util.arrays; import java.util.list; import android.app.activity; import android.os.bundle; import android.support.v7.widget.linearlayoutmanager; import android.support.v7.widget.recyclerview; import android.view.window; public class mainactivity extends activity { private recyclerview mrecyclerview; private galleryadapter madapter; private list<integer> mdatas; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); requestwindowfeature(window.feature_no_title); setcontentview(r.layout.activity_main); initdatas(); //得到控件 mrecyclerview = (recyclerview) findviewbyid(r.id.id_recyclerview_horizontal); //设置布局管理器 linearlayoutmanager linearlayoutmanager = new linearlayoutmanager(this); linearlayoutmanager.setorientation(linearlayoutmanager.horizontal); mrecyclerview.setlayoutmanager(linearlayoutmanager); //设置适配器 madapter = new galleryadapter(this, mdatas); mrecyclerview.setadapter(madapter); } private void initdatas() { mdatas = new arraylist<integer>(arrays.aslist(r.drawable.a, r.drawable.b, r.drawable.c, r.drawable.d, r.drawable.e, r.drawable.f, r.drawable.g, r.drawable.h, r.drawable.l)); } }
使用起来也很方便,唯一的区别就是要设置layoutmanager,目前只有一个实现类,就是linearlayoutmanager,可以设置为水平或者垂直。
最后效果图:
效果很不错,这就是recyclerview的基本用法了,但是你会发现一个坑爹的地方,竟然没有提供setonitemclicklistener这个回调,要不要这么坑爹。。。
2、为recyclerview添加onitemclicklistener回调
虽然它没有提供,但是添加个onitemclicklistener对我们来说还不是小菜一碟~
我决定在adapter中添加这个回调接口:
package com.example.zhy_horizontalscrollview03; import java.util.list; import android.content.context; import android.support.v7.widget.recyclerview; import android.view.layoutinflater; import android.view.view; import android.view.view.onclicklistener; import android.view.viewgroup; import android.widget.imageview; import android.widget.textview; public class galleryadapter extends recyclerview.adapter<galleryadapter.viewholder> { /** * itemclick的回调接口 * @author zhy * */ public interface onitemclicklitener { void onitemclick(view view, int position); } private onitemclicklitener monitemclicklitener; public void setonitemclicklitener(onitemclicklitener monitemclicklitener) { this.monitemclicklitener = monitemclicklitener; } private layoutinflater minflater; private list<integer> mdatas; public galleryadapter(context context, list<integer> datats) { minflater = layoutinflater.from(context); mdatas = datats; } public static class viewholder extends recyclerview.viewholder { public viewholder(view arg0) { super(arg0); } imageview mimg; textview mtxt; } @override public int getitemcount() { return mdatas.size(); } @override public viewholder oncreateviewholder(viewgroup viewgroup, int i) { view view = minflater.inflate(r.layout.activity_index_gallery_item, viewgroup, false); viewholder viewholder = new viewholder(view); viewholder.mimg = (imageview) view .findviewbyid(r.id.id_index_gallery_item_image); return viewholder; } @override public void onbindviewholder(final viewholder viewholder, final int i) { viewholder.mimg.setimageresource(mdatas.get(i)); //如果设置了回调,则设置点击事件 if (monitemclicklitener != null) { viewholder.itemview.setonclicklistener(new onclicklistener() { @override public void onclick(view v) { monitemclicklitener.onitemclick(viewholder.itemview, i); } }); } } }
很简单,创建一个接口,提供一个设置入口,然后在onbindviewholder中判断即可。
最后在主activity中设置监听:
madapter = new galleryadapter(this, mdatas); madapter.setonitemclicklitener(new onitemclicklitener() { @override public void onitemclick(view view, int position) { toast.maketext(mainactivity.this, position+"", toast.length_short) .show(); } }); mrecyclerview.setadapter(madapter);
好了,这样就行了,看效果图:
效果还是不错的,接下来我想改成相册效果,即上面显示一张大图,下面的recyclerview做为图片切换的指示器。
3、自定义recyclerview实现滚动时内容联动
首先修改下布局:
布局文件:
<linearlayout 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:orientation="vertical" > <framelayout android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1" > <imageview android:id="@+id/id_content" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_gravity="center" android:layout_margin="10dp" android:scaletype="centercrop" android:src="@drawable/ic_launcher" /> </framelayout> <com.example.zhy_horizontalscrollview03.myrecyclerview android:id="@+id/id_recyclerview_horizontal" android:layout_width="match_parent" android:layout_height="120dp" android:layout_gravity="bottom" android:background="#ff0000" android:scrollbars="none" /> </linearlayout>
添加一个显示大图的区域,把recyclerview改为自己定义的。
然后看我们自定义recyclerview的代码:
package com.example.zhy_horizontalscrollview03; import android.content.context; import android.support.v7.widget.recyclerview; import android.util.attributeset; import android.view.motionevent; import android.view.view; public class copyofmyrecyclerview extends recyclerview { public copyofmyrecyclerview(context context, attributeset attrs) { super(context, attrs); } private view mcurrentview; /** * 滚动时回调的接口 */ private onitemscrollchangelistener mitemscrollchangelistener; public void setonitemscrollchangelistener( onitemscrollchangelistener mitemscrollchangelistener) { this.mitemscrollchangelistener = mitemscrollchangelistener; } public interface onitemscrollchangelistener { void onchange(view view, int position); } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { super.onlayout(changed, l, t, r, b); mcurrentview = getchildat(0); if (mitemscrollchangelistener != null) { mitemscrollchangelistener.onchange(mcurrentview, getchildposition(mcurrentview)); } } @override public boolean ontouchevent(motionevent e) { if (e.getaction() == motionevent.action_move) { mcurrentview = getchildat(0); // log.e("tag", getchildposition(getchildat(0)) + ""); if (mitemscrollchangelistener != null) { mitemscrollchangelistener.onchange(mcurrentview, getchildposition(mcurrentview)); } } return super.ontouchevent(e); } }
定义了一个滚动时回调的接口,然后在ontouchevent中,监听action_move,用户手指滑动时,不断把当前第一个view回调回去~
关于我咋知道getchildat(0)和getchildposition()可用,起初我以为有getfirstvisibleitem这个方法,后来发现么有;但是发现了getrecycledviewpool()看名字我觉得是viewholder那个缓存队列,我想那么直接取这个队列的第一个不就是我要的view么,后来没有成功。我就观察它内部的view,最后发现,第一个显示的始终是它第一个child,至于getchildposition这个看方法就看出来了。
现在的效果:
和我之前那个例子的效果是一模一样的,不过,我还想做一些改变,我觉得gallery或者说相册的指示器,下面可能1000来张图片,我不仅喜欢手指在屏幕上滑动时,图片会自动切换。我还希望,如果我给指示器一个加速度,即使手指离开,下面还在滑动,上面也会联动 。而且我还想做些优化,直接在action_move中回调,触发的频率太高了,理论上一张图片只会触发一次~~
4、优化与打造真正的gallery效果
既然希望手指离开还能联动,那么不仅需要action_move需要监听,还得监听一个加速度,速度到达一定值,然后继续移动~~再理一理,需要这么麻烦么,不是能滚动么,那么应该有onscrolllistener啊,小看一把,果然有,哈哈哈~天助我也,下面看修改后的代码:
package com.example.zhy_horizontalscrollview03; import android.content.context; import android.support.v7.widget.recyclerview; import android.support.v7.widget.recyclerview.onscrolllistener; import android.util.attributeset; import android.view.view; public class myrecyclerview extends recyclerview implements onscrolllistener { /** * 记录当前第一个view */ private view mcurrentview; private onitemscrollchangelistener mitemscrollchangelistener; public void setonitemscrollchangelistener( onitemscrollchangelistener mitemscrollchangelistener) { this.mitemscrollchangelistener = mitemscrollchangelistener; } public interface onitemscrollchangelistener { void onchange(view view, int position); } public myrecyclerview(context context, attributeset attrs) { super(context, attrs); // todo auto-generated constructor stub this.setonscrolllistener(this); } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { super.onlayout(changed, l, t, r, b); mcurrentview = getchildat(0); if (mitemscrollchangelistener != null) { mitemscrollchangelistener.onchange(mcurrentview, getchildposition(mcurrentview)); } } @override public void onscrollstatechanged(int arg0) { } /** * * 滚动时,判断当前第一个view是否发生变化,发生才回调 */ @override public void onscrolled(int arg0, int arg1) { view newview = getchildat(0); if (mitemscrollchangelistener != null) { if (newview != null && newview != mcurrentview) { mcurrentview = newview ; mitemscrollchangelistener.onchange(mcurrentview, getchildposition(mcurrentview)); } } } }
我放弃了重写ontouchevent方法,而是让这个类实现recyclerview.onscrolllistener接口,然后设置监听,在onscrolled里面进行判断。
至于优化:我使用了一个成员变化存储当前第一个view,只有第一个view发生变化时才回调~~太完美了~
看mainactivity:
package com.example.zhy_horizontalscrollview03; import java.util.arraylist; import java.util.arrays; import java.util.list; import android.app.activity; import android.os.bundle; import android.support.v7.widget.linearlayoutmanager; import android.support.v7.widget.recyclerview; import android.view.view; import android.view.window; import android.widget.imageview; import android.widget.toast; import com.example.zhy_horizontalscrollview03.galleryadapter.onitemclicklitener; import com.example.zhy_horizontalscrollview03.myrecyclerview.onitemscrollchangelistener; public class mainactivity extends activity { private myrecyclerview mrecyclerview; private galleryadapter madapter; private list<integer> mdatas; private imageview mimg ; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); requestwindowfeature(window.feature_no_title); setcontentview(r.layout.activity_main); mimg = (imageview) findviewbyid(r.id.id_content); mdatas = new arraylist<integer>(arrays.aslist(r.drawable.a, r.drawable.b, r.drawable.c, r.drawable.d, r.drawable.e, r.drawable.f, r.drawable.g, r.drawable.h, r.drawable.l)); mrecyclerview = (myrecyclerview) findviewbyid(r.id.id_recyclerview_horizontal); linearlayoutmanager linearlayoutmanager = new linearlayoutmanager(this); linearlayoutmanager.setorientation(linearlayoutmanager.horizontal); mrecyclerview.setlayoutmanager(linearlayoutmanager); madapter = new galleryadapter(this, mdatas); mrecyclerview.setadapter(madapter); mrecyclerview.setonitemscrollchangelistener(new onitemscrollchangelistener() { @override public void onchange(view view, int position) { mimg.setimageresource(mdatas.get(position)); }; }); madapter.setonitemclicklitener(new onitemclicklitener() { @override public void onitemclick(view view, int position) { // toast.maketext(getapplicationcontext(), position + "", toast.length_short) // .show(); mimg.setimageresource(mdatas.get(position)); } }); } }
代码没什么变化~多了个设置回调~
效果图:
可以看到不仅支持手机在上面移动时的变化,如果我给了一个加速度,下面持续滚动,上面也会持续变化~~大赞~每张图片回调一次,效率也相当不错。
好了,看完这边博客,相信大家对于recyclerview有了一定的认识,甚至对于如何改造一个控件也多了一份了解~~
上一篇: infobright导入数据遇到特殊字符报错的解决方法
下一篇: Java 继承方法实例详解