Android应用中使用ContentProvider扫描本地图片并显示
之前群里面有朋友问我,有没有关于本地图片选择的demo,类似微信的效果,他说网上没有这方面的demo,问我能不能写一篇关于这个效果的demo,于是我研究了下微信的本地图片选择的demo,自己仿照的写了下分享给大家,希望对以后有这样子需求的朋友有一点帮助吧,主要使用的是contentprovider扫描手机中的图片,并用gridview将图片显示出来,关于gridview和listview显示图片的问题,一直是一个很头疼的问题,因为我们手机的内存有限,手机给每个应用程序分配的内存也有限,所以图片多的情况下很容易伴随着oom的发生,不过现在也有很多的开源的图片显示框架,对显示很多图片进行了优化,大家有兴趣的可以去了解了解,今天我的这篇文章使用的是lrucache这个类以及对图片进行相对应的裁剪,这样也可以尽量的避免oom的发生,我们先看下微信的效果吧
接下来我们就来实现这些效果吧,首先我们新建一个项目,取名imagescan
首先我们先看第一个界面吧,使用将手机中的图片扫描出来,然后根据图片的所在的文件夹将其分类出来,并显示所在文件夹里面的一张图片和文件夹中图片个数,我们根据界面元素(文件夹名, 文件夹图片个数,文件夹中的一张图片)使用一个实体对象imagebean来封装这三个属性
package com.example.imagescan; /** * gridview的每个item的数据对象 * * @author len * */ public class imagebean{ /** * 文件夹的第一张图片路径 */ private string topimagepath; /** * 文件夹名 */ private string foldername; /** * 文件夹中的图片数 */ private int imagecounts; public string gettopimagepath() { return topimagepath; } public void settopimagepath(string topimagepath) { this.topimagepath = topimagepath; } public string getfoldername() { return foldername; } public void setfoldername(string foldername) { this.foldername = foldername; } public int getimagecounts() { return imagecounts; } public void setimagecounts(int imagecounts) { this.imagecounts = imagecounts; } }
接下来就是主界面的布局啦,上面的导航栏我没有加进去,只有下面的gridview,所以说主界面布局中只有一个gridview
<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" > <gridview android:id="@+id/main_grid" android:layout_width="match_parent" android:layout_height="match_parent" android:listselector="@android:color/transparent" android:cachecolorhint="@android:color/transparent" android:stretchmode="columnwidth" android:horizontalspacing="20dip" android:gravity="center" android:verticalspacing="20dip" android:columnwidth="90dip" android:numcolumns="2" > </gridview> </relativelayout>
接下来就是gridview的item的布局,看上面的图也行你会认为他的效果是2张图片添加的效果,其实不是,后面的叠加效果只是一张背景图片而已,代码先贴上来
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <framelayout android:id="@+id/framelayout" android:layout_width="fill_parent" android:layout_height="wrap_content" > <com.example.imagescan.myimageview android:id="@+id/group_image" android:background="@drawable/albums_bg" android:src="@drawable/friends_sends_pictures_no" android:paddingleft="20dip" android:paddingright="20dip" android:paddingtop="18dip" android:paddingbottom="30dip" android:scaletype="fitxy" android:layout_width="fill_parent" android:layout_height="150dip" /> <textview android:id="@+id/group_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/albums_icon_bg" android:gravity="center" android:layout_marginbottom="10dip" android:text="5" android:layout_gravity="bottom|center_horizontal" /> </framelayout> <textview android:id="@+id/group_title" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:layout_below="@id/framelayout" android:layout_centerhorizontal="true" android:ellipsize="end" android:singleline="true" android:text="camera" android:textsize="16sp" /> </relativelayout>
看到上面的布局代码,也行你已经发现了,上面使用的是自定义的myimageview,我先不说这个自定义myimageview的作用,待会再给大家说,我们继续看代码
第一个界面的主要代码
package com.example.imagescan; import java.io.file; import java.util.arraylist; import java.util.hashmap; import java.util.iterator; import java.util.list; import java.util.map; import android.app.activity; import android.app.progressdialog; import android.content.contentresolver; import android.content.intent; import android.database.cursor; import android.net.uri; import android.os.bundle; import android.os.handler; import android.os.message; import android.provider.mediastore; import android.view.view; import android.widget.adapterview; import android.widget.adapterview.onitemclicklistener; import android.widget.gridview; public class mainactivity extends activity { private hashmap<string, list<string>> mgruopmap = new hashmap<string, list<string>>(); private list<imagebean> list = new arraylist<imagebean>(); private final static int scan_ok = 1; private progressdialog mprogressdialog; private groupadapter adapter; private gridview mgroupgridview; private handler mhandler = new handler(){ @override public void handlemessage(message msg) { super.handlemessage(msg); switch (msg.what) { case scan_ok: //关闭进度条 mprogressdialog.dismiss(); adapter = new groupadapter(mainactivity.this, list = subgroupofimage(mgruopmap), mgroupgridview); mgroupgridview.setadapter(adapter); break; } } }; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mgroupgridview = (gridview) findviewbyid(r.id.main_grid); getimages(); mgroupgridview.setonitemclicklistener(new onitemclicklistener() { @override public void onitemclick(adapterview<?> parent, view view, int position, long id) { list<string> childlist = mgruopmap.get(list.get(position).getfoldername()); intent mintent = new intent(mainactivity.this, showimageactivity.class); mintent.putstringarraylistextra("data", (arraylist<string>)childlist); startactivity(mintent); } }); }
/** * 利用contentprovider扫描手机中的图片,此方法在运行在子线程中 */ private void getimages() { //显示进度条 mprogressdialog = progressdialog.show(this, null, "正在加载..."); new thread(new runnable() { @override public void run() { uri mimageuri = mediastore.images.media.external_content_uri; contentresolver mcontentresolver = mainactivity.this.getcontentresolver(); //只查询jpeg和png的图片 cursor mcursor = mcontentresolver.query(mimageuri, null, mediastore.images.media.mime_type + "=? or " + mediastore.images.media.mime_type + "=?", new string[] { "image/jpeg", "image/png" }, mediastore.images.media.date_modified); if(mcursor == null){ return; } while (mcursor.movetonext()) { //获取图片的路径 string path = mcursor.getstring(mcursor .getcolumnindex(mediastore.images.media.data)); //获取该图片的父路径名 string parentname = new file(path).getparentfile().getname(); //根据父路径名将图片放入到mgruopmap中 if (!mgruopmap.containskey(parentname)) { list<string> chilelist = new arraylist<string>(); chilelist.add(path); mgruopmap.put(parentname, chilelist); } else { mgruopmap.get(parentname).add(path); } } //通知handler扫描图片完成 mhandler.sendemptymessage(scan_ok); mcursor.close(); } }).start(); }
/** * 组装分组界面gridview的数据源,因为我们扫描手机的时候将图片信息放在hashmap中 * 所以需要遍历hashmap将数据组装成list * * @param mgruopmap * @return */ private list<imagebean> subgroupofimage(hashmap<string, list<string>> mgruopmap){ if(mgruopmap.size() == 0){ return null; } list<imagebean> list = new arraylist<imagebean>(); iterator<map.entry<string, list<string>>> it = mgruopmap.entryset().iterator(); while (it.hasnext()) { map.entry<string, list<string>> entry = it.next(); imagebean mimagebean = new imagebean(); string key = entry.getkey(); list<string> value = entry.getvalue(); mimagebean.setfoldername(key); mimagebean.setimagecounts(value.size()); mimagebean.settopimagepath(value.get(0));//获取该组的第一张图片 list.add(mimagebean); } return list; } }
然后是subgroupofimage()方法,改方法是将mgruopmap的数据组装到list中,在list中存放gridview中的每个item的数据对象imagebean, 遍历hashmap对象,具体的逻辑看代码,之后就是给gridview设置adapter。
设置item点击事件,点击文件夹跳转到展示文件夹图片的activity, 我们需要传递每个文件夹中的图片的路径的集合
看groupadapter的代码之前,我们先看一个比较重要的类,本地图片加载器nativeimageloader
package com.example.imagescan; import java.util.concurrent.executorservice; import java.util.concurrent.executors; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.graphics.point; import android.os.handler; import android.os.message; import android.support.v4.util.lrucache; /** * 本地图片加载器,采用的是异步解析本地图片,单例模式利用getinstance()获取nativeimageloader实例 * 调用loadnativeimage()方法加载本地图片,此类可作为一个加载本地图片的工具类 */ public class nativeimageloader { private lrucache<string, bitmap> mmemorycache; private static nativeimageloader minstance = new nativeimageloader(); private executorservice mimagethreadpool = executors.newfixedthreadpool(1); private nativeimageloader(){ //获取应用程序的最大内存 final int maxmemory = (int) (runtime.getruntime().maxmemory() / 1024); //用最大内存的1/4来存储图片 final int cachesize = maxmemory / 4; mmemorycache = new lrucache<string, bitmap>(cachesize) { //获取每张图片的大小 @override protected int sizeof(string key, bitmap bitmap) { return bitmap.getrowbytes() * bitmap.getheight() / 1024; } }; } /** * 通过此方法来获取nativeimageloader的实例 * @return */ public static nativeimageloader getinstance(){ return minstance; }
/** * 加载本地图片,对图片不进行裁剪 * @param path * @param mcallback * @return */ public bitmap loadnativeimage(final string path, final nativeimagecallback mcallback){ return this.loadnativeimage(path, null, mcallback); } /** * 此方法来加载本地图片,这里的mpoint是用来封装imageview的宽和高,我们会根据imageview控件的大小来裁剪bitmap * 如果你不想裁剪图片,调用loadnativeimage(final string path, final nativeimagecallback mcallback)来加载 * @param path * @param mpoint * @param mcallback * @return */ public bitmap loadnativeimage(final string path, final point mpoint, final nativeimagecallback mcallback){ //先获取内存中的bitmap bitmap bitmap = getbitmapfrommemcache(path); final handler mhander = new handler(){ @override public void handlemessage(message msg) { super.handlemessage(msg); mcallback.onimageloader((bitmap)msg.obj, path); } }; //若该bitmap不在内存缓存中,则启用线程去加载本地的图片,并将bitmap加入到mmemorycache中 if(bitmap == null){ mimagethreadpool.execute(new runnable() { @override public void run() { //先获取图片的缩略图 bitmap mbitmap = decodethumbbitmapforfile(path, mpoint == null ? 0: mpoint.x, mpoint == null ? 0: mpoint.y); message msg = mhander.obtainmessage(); msg.obj = mbitmap; mhander.sendmessage(msg); //将图片加入到内存缓存 addbitmaptomemorycache(path, mbitmap); } }); } return bitmap; } /** * 往内存缓存中添加bitmap * * @param key * @param bitmap */ private void addbitmaptomemorycache(string key, bitmap bitmap) { if (getbitmapfrommemcache(key) == null && bitmap != null) { mmemorycache.put(key, bitmap); } } /** * 根据key来获取内存中的图片 * @param key * @return */ private bitmap getbitmapfrommemcache(string key) { return mmemorycache.get(key); } /** * 根据view(主要是imageview)的宽和高来获取图片的缩略图 * @param path * @param viewwidth * @param viewheight * @return */ private bitmap decodethumbbitmapforfile(string path, int viewwidth, int viewheight){ bitmapfactory.options options = new bitmapfactory.options(); //设置为true,表示解析bitmap对象,该对象不占内存 options.injustdecodebounds = true; bitmapfactory.decodefile(path, options); //设置缩放比例 options.insamplesize = computescale(options, viewwidth, viewheight); //设置为false,解析bitmap对象加入到内存中 options.injustdecodebounds = false; return bitmapfactory.decodefile(path, options); } /** * 根据view(主要是imageview)的宽和高来计算bitmap缩放比例。默认不缩放 * @param options * @param width * @param height */ private int computescale(bitmapfactory.options options, int viewwidth, int viewheight){ int insamplesize = 1; if(viewwidth == 0 || viewwidth == 0){ return insamplesize; } int bitmapwidth = options.outwidth; int bitmapheight = options.outheight; //假如bitmap的宽度或高度大于我们设定图片的view的宽高,则计算缩放比例 if(bitmapwidth > viewwidth || bitmapheight > viewwidth){ int widthscale = math.round((float) bitmapwidth / (float) viewwidth); int heightscale = math.round((float) bitmapheight / (float) viewwidth); //为了保证图片不缩放变形,我们取宽高比例最小的那个 insamplesize = widthscale < heightscale ? widthscale : heightscale; } return insamplesize; } /** * 加载本地图片的回调接口 * * @author xiaanming * */ public interface nativeimagecallback{ /** * 当子线程加载完了本地的图片,将bitmap和图片路径回调在此方法中 * @param bitmap * @param path */ public void onimageloader(bitmap bitmap, string path); } }
computescale()计算图片需要裁剪的比例,根据控件的大小和图片的大小确定比例,如果图片比控件大,我们就进行裁剪,否则不需要。
decodethumbbitmapforfile()方法是根据计算好了图片裁剪的比例之后从文件中加载图片,我们先设置options.injustdecodebounds = true表示解析不占用内存,但是我们能获取图片的具体大小,利用computescale()计算好比例,在将options.injustdecodebounds=false,再次解析bitmap,这样子就对图片进行了裁剪。
loadnativeimage(final string path, final point mpoint, final nativeimagecallback mcallback)我们在客户端只需要调用该方法就能获取到bitmap对象,里面的具体逻辑是先判断内存缓存lrucache中是否存在该bitmap,不存在就开启子线程去读取,为了方便管理加载本地图片线程,这里使用了线程池,池中只能容纳一个线程,读取完了本地图片先将bitmap加入到lrucache中,保存的key为图片路径,然后再使用handler通知主线程图片加载好了,之后将bitmap和路径回调到方法onimageloader(bitmap bitmap, string path)中,该方法的mpoint是用来封装控件的宽和高的对象
如果不对图片进行裁剪直接这个方法的重载方法loadnativeimage(final string path, final nativeimagecallback mcallback) 就行了,逻辑是一样的,只是这个方法不对图片进行裁剪
接下来就是gridview的adapter类的代码
package com.example.imagescan; import java.util.list; import android.content.context; import android.graphics.bitmap; import android.graphics.point; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; import android.widget.baseadapter; import android.widget.gridview; import android.widget.imageview; import android.widget.textview; import com.example.imagescan.myimageview.onmeasurelistener; import com.example.imagescan.nativeimageloader.nativeimagecallback; public class groupadapter extends baseadapter{ private list<imagebean> list; private point mpoint = new point(0, 0);//用来封装imageview的宽和高的对象 private gridview mgridview; protected layoutinflater minflater; @override public int getcount() { return list.size(); } @override public object getitem(int position) { return list.get(position); } @override public long getitemid(int position) { return position; } public groupadapter(context context, list<imagebean> list, gridview mgridview){ this.list = list; this.mgridview = mgridview; minflater = layoutinflater.from(context); } @override public view getview(int position, view convertview, viewgroup parent) { final viewholder viewholder; imagebean mimagebean = list.get(position); string path = mimagebean.gettopimagepath(); if(convertview == null){ viewholder = new viewholder(); convertview = minflater.inflate(r.layout.grid_group_item, null); viewholder.mimageview = (myimageview) convertview.findviewbyid(r.id.group_image); viewholder.mtextviewtitle = (textview) convertview.findviewbyid(r.id.group_title); viewholder.mtextviewcounts = (textview) convertview.findviewbyid(r.id.group_count); //用来监听imageview的宽和高 viewholder.mimageview.setonmeasurelistener(new onmeasurelistener() { @override public void onmeasuresize(int width, int height) { mpoint.set(width, height); } }); convertview.settag(viewholder); }else{ viewholder = (viewholder) convertview.gettag(); viewholder.mimageview.setimageresource(r.drawable.friends_sends_pictures_no); } viewholder.mtextviewtitle.settext(mimagebean.getfoldername()); viewholder.mtextviewcounts.settext(integer.tostring(mimagebean.getimagecounts())); //给imageview设置路径tag,这是异步加载图片的小技巧 viewholder.mimageview.settag(path); //利用nativeimageloader类加载本地图片 bitmap bitmap = nativeimageloader.getinstance().loadnativeimage(path, mpoint, new nativeimagecallback() { @override public void onimageloader(bitmap bitmap, string path) { imageview mimageview = (imageview) mgridview.findviewwithtag(path); if(bitmap != null && mimageview != null){ mimageview.setimagebitmap(bitmap); } } }); if(bitmap != null){ viewholder.mimageview.setimagebitmap(bitmap); }else{ viewholder.mimageview.setimageresource(r.drawable.friends_sends_pictures_no); } return convertview; } public static class viewholder{ public myimageview mimageview; public textview mtextviewtitle; public textview mtextviewcounts; } }
但是我们想在getview()中获取imageview的宽和高存在问题,在getview()里面刚开始显示item的时候利用imageview.getwidth() 获取的都是0,为什么刚开始获取不到宽和高呢,因为我们使用layoutinflater来将xml布局文件inflater()成view的时候,view并没有显示在界面上面,表明并没有对view进行onmeasure(), onlayout(), ondraw()等操作,必须等到retrue convertview的时候,表示该item对应的view已经绘制在listview的位置上了, 此时才对item对应的view进行onmeasure(), onlayout(), ondraw()等操作,这时候才能获取到item的宽和高,于是我想到了自定义imageview,在onmeasure()中利用回调的模式主动通知我imageview测量的宽和高,但是这有一个小小的问题,就是显示gridview的第一个item的时候,获取的宽和高还是0,第二个就能正常获取了,第一个宽和高为0,表示我们不对第一张图片进行裁剪而已,在效率上也没啥问题,不知道大家有没有好的方法,可以在getview()中获取item中某个控件的宽和高。
自定义myimageview的代码,我们只需要设置onmeasurelistener监听,当myimageview测量完毕之后,就会将测量的宽和高回调到onmeasuresize()中,然后我们可以根据myimageview的大小来裁剪图片
package com.example.imagescan; import android.content.context; import android.util.attributeset; import android.widget.imageview; public class myimageview extends imageview { private onmeasurelistener onmeasurelistener; public void setonmeasurelistener(onmeasurelistener onmeasurelistener) { this.onmeasurelistener = onmeasurelistener; } public myimageview(context context, attributeset attrs) { super(context, attrs); } public myimageview(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); //将图片测量的大小回调到onmeasuresize()方法中 if(onmeasurelistener != null){ onmeasurelistener.onmeasuresize(getmeasuredwidth(), getmeasuredheight()); } } public interface onmeasurelistener{ public void onmeasuresize(int width, int height); } }
上面这些代码就完成了第一个界面的功能了,接下来就是点击gridview的item跳转另一个界面来显示该文件夹下面的所有图片,功能跟第一个界面差不多,也是使用gridview来显示图片,第二个界面的布局代码我就不贴了,直接贴上界面的代码
package com.example.imagescan; import java.util.list; import android.app.activity; import android.os.bundle; import android.widget.gridview; import android.widget.toast; public class showimageactivity extends activity { private gridview mgridview; private list<string> list; private childadapter adapter; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.show_image_activity); mgridview = (gridview) findviewbyid(r.id.child_grid); list = getintent().getstringarraylistextra("data"); adapter = new childadapter(this, list, mgridview); mgridview.setadapter(adapter); } @override public void onbackpressed() { toast.maketext(this, "选中 " + adapter.getselectitems().size() + " item", toast.length_long).show(); super.onbackpressed(); } }
gridview的item上面一个我们自定义的myimageview用来显示图片,另外还有一个checkbox来记录我们选中情况,adapter的代码如下
package com.example.imagescan; import java.util.arraylist; import java.util.hashmap; import java.util.iterator; import java.util.list; import java.util.map; import android.content.context; import android.graphics.bitmap; import android.graphics.point; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; import android.widget.baseadapter; import android.widget.checkbox; import android.widget.compoundbutton; import android.widget.imageview; import android.widget.compoundbutton.oncheckedchangelistener; import android.widget.gridview; import com.example.imagescan.myimageview.onmeasurelistener; import com.example.imagescan.nativeimageloader.nativeimagecallback; import com.nineoldandroids.animation.animatorset; import com.nineoldandroids.animation.objectanimator; public class childadapter extends baseadapter { private point mpoint = new point(0, 0);//用来封装imageview的宽和高的对象 /** * 用来存储图片的选中情况 */ private hashmap<integer, boolean> mselectmap = new hashmap<integer, boolean>(); private gridview mgridview; private list<string> list; protected layoutinflater minflater; public childadapter(context context, list<string> list, gridview mgridview) { this.list = list; this.mgridview = mgridview; minflater = layoutinflater.from(context); } @override public int getcount() { return list.size(); } @override public object getitem(int position) { return list.get(position); } @override public long getitemid(int position) { return position; } @override public view getview(final int position, view convertview, viewgroup parent) { final viewholder viewholder; string path = list.get(position); if(convertview == null){ convertview = minflater.inflate(r.layout.grid_child_item, null); viewholder = new viewholder(); viewholder.mimageview = (myimageview) convertview.findviewbyid(r.id.child_image); viewholder.mcheckbox = (checkbox) convertview.findviewbyid(r.id.child_checkbox); //用来监听imageview的宽和高 viewholder.mimageview.setonmeasurelistener(new onmeasurelistener() { @override public void onmeasuresize(int width, int height) { mpoint.set(width, height); } }); convertview.settag(viewholder); }else{ viewholder = (viewholder) convertview.gettag(); viewholder.mimageview.setimageresource(r.drawable.friends_sends_pictures_no); } viewholder.mimageview.settag(path); viewholder.mcheckbox.setoncheckedchangelistener(new oncheckedchangelistener() { @override public void oncheckedchanged(compoundbutton buttonview, boolean ischecked) { //如果是未选中的checkbox,则添加动画 if(!mselectmap.containskey(position) || !mselectmap.get(position)){ addanimation(viewholder.mcheckbox); } mselectmap.put(position, ischecked); } }); viewholder.mcheckbox.setchecked(mselectmap.containskey(position) ? mselectmap.get(position) : false); //利用nativeimageloader类加载本地图片 bitmap bitmap = nativeimageloader.getinstance().loadnativeimage(path, mpoint, new nativeimagecallback() { @override public void onimageloader(bitmap bitmap, string path) { imageview mimageview = (imageview) mgridview.findviewwithtag(path); if(bitmap != null && mimageview != null){ mimageview.setimagebitmap(bitmap); } } }); if(bitmap != null){ viewholder.mimageview.setimagebitmap(bitmap); }else{ viewholder.mimageview.setimageresource(r.drawable.friends_sends_pictures_no); } return convertview; } /** * 给checkbox加点击动画,利用开源库nineoldandroids设置动画 * @param view */ private void addanimation(view view){ float [] vaules = new float[]{0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f, 1.3f, 1.25f, 1.2f, 1.15f, 1.1f, 1.0f}; animatorset set = new animatorset(); set.playtogether(objectanimator.offloat(view, "scalex", vaules), objectanimator.offloat(view, "scaley", vaules)); set.setduration(150); set.start(); } /** * 获取选中的item的position * @return */ public list<integer> getselectitems(){ list<integer> list = new arraylist<integer>(); for(iterator<map.entry<integer, boolean>> it = mselectmap.entryset().iterator(); it.hasnext();){ map.entry<integer, boolean> entry = it.next(); if(entry.getvalue()){ list.add(entry.getkey()); } } return list; } public static class viewholder{ public myimageview mimageview; public checkbox mcheckbox; } }
第二个界面的adapter跟第一个界面差不多,无非多了一个checkbox用来记录图片选择情况,我们只需要对checkbox设置setoncheckedchangelistener监听,微信的选中之后checkbox有一个动画效果,所以我利用nineoldandroids动画库也给checkbox加了一个动画效果,直接调用addanimation()方法就能添加了,getselectitems()方法就能获取我们选中的item的position了,知道了选中的position,其他的信息就都知道了,微信有对图片进行预览的功能,我这里就不添加了,如果有这个需求可以自行添加,给大家推荐一个https://github.com/chrisbanes/photoview
运行项目,效果如下
看起来还不错吧,采用的是异步读取图片,对图片进行了缓存和裁剪,使得在显示本地图片方面比较流畅,gridview滑动也挺流畅的,也有效的避免oom的产生。