欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android应用中使用ContentProvider扫描本地图片并显示

程序员文章站 2024-02-29 11:39:10
之前群里面有朋友问我,有没有关于本地图片选择的demo,类似微信的效果,他说网上没有这方面的demo,问我能不能写一篇关于这个效果的demo,于是我研究了下微信的本地图片选...

之前群里面有朋友问我,有没有关于本地图片选择的demo,类似微信的效果,他说网上没有这方面的demo,问我能不能写一篇关于这个效果的demo,于是我研究了下微信的本地图片选择的demo,自己仿照的写了下分享给大家,希望对以后有这样子需求的朋友有一点帮助吧,主要使用的是contentprovider扫描手机中的图片,并用gridview将图片显示出来,关于gridview和listview显示图片的问题,一直是一个很头疼的问题,因为我们手机的内存有限,手机给每个应用程序分配的内存也有限,所以图片多的情况下很容易伴随着oom的发生,不过现在也有很多的开源的图片显示框架,对显示很多图片进行了优化,大家有兴趣的可以去了解了解,今天我的这篇文章使用的是lrucache这个类以及对图片进行相对应的裁剪,这样也可以尽量的避免oom的发生,我们先看下微信的效果吧

Android应用中使用ContentProvider扫描本地图片并显示

Android应用中使用ContentProvider扫描本地图片并显示

接下来我们就来实现这些效果吧,首先我们新建一个项目,取名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; 
     
  } 
 
 
} 
首先看getimages()这个方法,该方法是使用contentprovider将手机中的图片扫描出来,我这里只扫描了手机的外部存储中的图片,由于手机中可能存在很多的图片,扫描图片又比较耗时,所以我们在这里开启了子线程去获取图片,扫描的图片都存放在cursor中,我们先要将图片按照文件夹进行分类,我们使用了hashmap来进行分类并将结果存储到mgruopmap(key是文件夹名,value是文件夹中的图片路径的list)中,分类完了关闭cursor并利用handler来通知主线程
然后是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); 
  } 
} 
该类是一个单例类,提供了本地图片加载,内存缓存,裁剪等逻辑,该类在加载本地图片的时候采用的是异步加载的方式,对于大图片的加载也是比较耗时的,所以采用子线程的方式去加载,对于图片的缓存机制使用的是lrucache,使用手机分配给应用程序内存的1/4用来缓存图片,除了使用lrucache缓存图片之外,还对图片进行了裁剪,举个很简单的例子,假如我们的控件大小是100 * 100, 而我们的图片是400*400,我们加载这么大的图片需要很多的内存,所以我们采用了图片裁剪,根据控件的大小来确定图片的裁剪比例,从而减小内存的消耗,提高gridview滑动的流畅度,介绍里面几个比较重要的方法
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; 
  } 
 
   
} 
首先我们将每个item的图片路径设置tag到该imageview上面,然后利用nativeimageloader来加载本地图片,但是我们显示的图片的宽和高可能远大于girdview item中imageview的大小,于是为了节省内存,我们需要对图片进行裁剪,需要对图片裁剪我们利用loadnativeimage(final string path, final point mpoint, final nativeimagecallback mcallback)方法,我们就必须要获取imageview的宽和高了
但是我们想在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

运行项目,效果如下

Android应用中使用ContentProvider扫描本地图片并显示

看起来还不错吧,采用的是异步读取图片,对图片进行了缓存和裁剪,使得在显示本地图片方面比较流畅,gridview滑动也挺流畅的,也有效的避免oom的产生。