Android 使用ContentProvider扫描手机中的图片,仿微信显示本地图片效果

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介:

转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/18730223),请尊重他人的辛勤劳动成果,谢谢!

写这篇文章之前,先简单说几句,首先是先恭喜下自己获得了2013年的博客之星称号,很意外也很开心,自己是从2013年开始写博客,那时候也不知道怎么写,我从小就不喜欢写日记,作文什么的,所以刚开始都是贴代码,也没有人看,后面慢慢的,写的文章被推荐博客首页和CSDN首页(这里也要小小的感谢下小编MM),访问量逐渐的多了起来,有更多的人看我的文章,这也使自己有了继续写文章的动力,也希望我写的东西对大家有点帮助吧,在2014年我会继续在CSDN上面写博客,然后是感谢博客之星给我投票支持我的朋友们,谢谢你们支持我的每一票,最后就是2014春节马上就到了,提前祝福大家新年快乐,工作顺利,事事顺心!

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


接下来我们就来实现这些效果吧,首先我们新建一个项目,取名ImageScan

首先我们先看第一个界面吧,使用将手机中的图片扫描出来,然后根据图片的所在的文件夹将其分类出来,并显示所在文件夹里面的一张图片和文件夹中图片个数,我们根据界面元素(文件夹名, 文件夹图片个数,文件夹中的一张图片)使用一个实体对象ImageBean来封装这三个属性

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.example.imagescan;  
  2.   
  3. /** 
  4.  * GridView的每个item的数据对象 
  5.  *  
  6.  * @author len 
  7.  * 
  8.  */  
  9. public class ImageBean{  
  10.     /** 
  11.      * 文件夹的第一张图片路径 
  12.      */  
  13.     private String topImagePath;  
  14.     /** 
  15.      * 文件夹名 
  16.      */  
  17.     private String folderName;   
  18.     /** 
  19.      * 文件夹中的图片数 
  20.      */  
  21.     private int imageCounts;  
  22.       
  23.     public String getTopImagePath() {  
  24.         return topImagePath;  
  25.     }  
  26.     public void setTopImagePath(String topImagePath) {  
  27.         this.topImagePath = topImagePath;  
  28.     }  
  29.     public String getFolderName() {  
  30.         return folderName;  
  31.     }  
  32.     public void setFolderName(String folderName) {  
  33.         this.folderName = folderName;  
  34.     }  
  35.     public int getImageCounts() {  
  36.         return imageCounts;  
  37.     }  
  38.     public void setImageCounts(int imageCounts) {  
  39.         this.imageCounts = imageCounts;  
  40.     }  
  41.       
  42. }  
接下来就是主界面的布局啦,上面的导航栏我没有加进去,只有下面的GridView,所以说主界面布局中只有一个GridView

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent" >  
  5.   
  6.     <GridView  
  7.         android:id="@+id/main_grid"  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="match_parent"  
  10.         android:listSelector="@android:color/transparent"  
  11.         android:cacheColorHint="@android:color/transparent"  
  12.         android:stretchMode="columnWidth"  
  13.         android:horizontalSpacing="20dip"  
  14.         android:gravity="center"  
  15.         android:verticalSpacing="20dip"  
  16.         android:columnWidth="90dip"  
  17.         android:numColumns="2" >  
  18.     </GridView>  
  19.   
  20. </RelativeLayout>  

接下来就是GridView的Item的布局,看上面的图也行你会认为他的效果是2张图片添加的效果,其实不是,后面的叠加效果只是一张背景图片而已,代码先贴上来

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="wrap_content" >  
  5.   
  6.     <FrameLayout  
  7.         android:id="@+id/framelayout"  
  8.         android:layout_width="fill_parent"  
  9.         android:layout_height="wrap_content" >  
  10.   
  11.         <com.example.imagescan.MyImageView  
  12.             android:id="@+id/group_image"  
  13.             android:background="@drawable/albums_bg"  
  14.             android:src="@drawable/friends_sends_pictures_no"  
  15.             android:paddingLeft="20dip"  
  16.             android:paddingRight="20dip"  
  17.             android:paddingTop="18dip"  
  18.             android:paddingBottom="30dip"  
  19.             android:scaleType="fitXY"  
  20.             android:layout_width="fill_parent"  
  21.             android:layout_height="150dip" />  
  22.   
  23.         <TextView  
  24.             android:id="@+id/group_count"  
  25.             android:layout_width="wrap_content"  
  26.             android:layout_height="wrap_content"  
  27.             android:background="@drawable/albums_icon_bg"  
  28.             android:gravity="center"  
  29.             android:layout_marginBottom="10dip"  
  30.             android:text="5"  
  31.             android:layout_gravity="bottom|center_horizontal" />  
  32.     </FrameLayout>  
  33.   
  34.     <TextView  
  35.         android:id="@+id/group_title"  
  36.         android:layout_width="fill_parent"  
  37.         android:layout_height="wrap_content"  
  38.         android:gravity="center"  
  39.         android:layout_below="@id/framelayout"  
  40.         android:layout_centerHorizontal="true"  
  41.         android:ellipsize="end"  
  42.         android:singleLine="true"  
  43.         android:text="Camera"  
  44.         android:textSize="16sp" />  
  45.   
  46. </RelativeLayout>  
看到上面的布局代码,也行你已经发现了,上面使用的是自定义的MyImageView,我先不说这个自定义MyImageView的作用,待会再给大家说,我们继续看代码

第一个界面的主要代码

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.example.imagescan;  
  2.   
  3. import java.io.File;  
  4. import java.util.ArrayList;  
  5. import java.util.HashMap;  
  6. import java.util.Iterator;  
  7. import java.util.List;  
  8. import java.util.Map;  
  9.   
  10. import android.app.Activity;  
  11. import android.app.ProgressDialog;  
  12. import android.content.ContentResolver;  
  13. import android.content.Intent;  
  14. import android.database.Cursor;  
  15. import android.net.Uri;  
  16. import android.os.Bundle;  
  17. import android.os.Handler;  
  18. import android.os.Message;  
  19. import android.provider.MediaStore;  
  20. import android.view.View;  
  21. import android.widget.AdapterView;  
  22. import android.widget.AdapterView.OnItemClickListener;  
  23. import android.widget.GridView;  
  24. /** 
  25.  * @blog http://blog.csdn.net/xiaanming 
  26.  *  
  27.  * @author xiaanming 
  28.  *  
  29.  * 
  30.  */  
  31. public class MainActivity extends Activity {  
  32.     private HashMap<String, List<String>> mGruopMap = new HashMap<String, List<String>>();  
  33.     private List<ImageBean> list = new ArrayList<ImageBean>();  
  34.     private final static int SCAN_OK = 1;  
  35.     private ProgressDialog mProgressDialog;  
  36.     private GroupAdapter adapter;  
  37.     private GridView mGroupGridView;  
  38.       
  39.     private Handler mHandler = new Handler(){  
  40.   
  41.         @Override  
  42.         public void handleMessage(Message msg) {  
  43.             super.handleMessage(msg);  
  44.             switch (msg.what) {  
  45.             case SCAN_OK:  
  46.                 //关闭进度条  
  47.                 mProgressDialog.dismiss();  
  48.                   
  49.                 adapter = new GroupAdapter(MainActivity.this, list = subGroupOfImage(mGruopMap), mGroupGridView);  
  50.                 mGroupGridView.setAdapter(adapter);  
  51.                 break;  
  52.             }  
  53.         }  
  54.           
  55.     };  
  56.   
  57.     @Override  
  58.     protected void onCreate(Bundle savedInstanceState) {  
  59.         super.onCreate(savedInstanceState);  
  60.         setContentView(R.layout.activity_main);  
  61.           
  62.         mGroupGridView = (GridView) findViewById(R.id.main_grid);  
  63.           
  64.         getImages();  
  65.           
  66.         mGroupGridView.setOnItemClickListener(new OnItemClickListener() {  
  67.   
  68.             @Override  
  69.             public void onItemClick(AdapterView<?> parent, View view,  
  70.                     int position, long id) {  
  71.                 List<String> childList = mGruopMap.get(list.get(position).getFolderName());  
  72.                   
  73.                 Intent mIntent = new Intent(MainActivity.this, ShowImageActivity.class);  
  74.                 mIntent.putStringArrayListExtra("data", (ArrayList<String>)childList);  
  75.                 startActivity(mIntent);  
  76.                   
  77.             }  
  78.         });  
  79.           
  80.     }  
  81.   
  82.   
  83.     /** 
  84.      * 利用ContentProvider扫描手机中的图片,此方法在运行在子线程中 
  85.      */  
  86.     private void getImages() {  
  87.         //显示进度条  
  88.         mProgressDialog = ProgressDialog.show(thisnull"正在加载...");  
  89.           
  90.         new Thread(new Runnable() {  
  91.               
  92.             @Override  
  93.             public void run() {  
  94.                 Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;  
  95.                 ContentResolver mContentResolver = MainActivity.this.getContentResolver();  
  96.   
  97.                 //只查询jpeg和png的图片  
  98.                 Cursor mCursor = mContentResolver.query(mImageUri, null,  
  99.                         MediaStore.Images.Media.MIME_TYPE + "=? or "  
  100.                                 + MediaStore.Images.Media.MIME_TYPE + "=?",  
  101.                         new String[] { "image/jpeg""image/png" }, MediaStore.Images.Media.DATE_MODIFIED);  
  102.                   
  103.                 if(mCursor == null){  
  104.                     return;  
  105.                 }  
  106.                   
  107.                 while (mCursor.moveToNext()) {  
  108.                     //获取图片的路径  
  109.                     String path = mCursor.getString(mCursor  
  110.                             .getColumnIndex(MediaStore.Images.Media.DATA));  
  111.                       
  112.                     //获取该图片的父路径名  
  113.                     String parentName = new File(path).getParentFile().getName();  
  114.   
  115.                       
  116.                     //根据父路径名将图片放入到mGruopMap中  
  117.                     if (!mGruopMap.containsKey(parentName)) {  
  118.                         List<String> chileList = new ArrayList<String>();  
  119.                         chileList.add(path);  
  120.                         mGruopMap.put(parentName, chileList);  
  121.                     } else {  
  122.                         mGruopMap.get(parentName).add(path);  
  123.                     }  
  124.                 }  
  125.                   
  126.                 //通知Handler扫描图片完成  
  127.                 mHandler.sendEmptyMessage(SCAN_OK);  
  128.                 mCursor.close();  
  129.             }  
  130.         }).start();  
  131.           
  132.     }  
  133.       
  134.       
  135.     /** 
  136.      * 组装分组界面GridView的数据源,因为我们扫描手机的时候将图片信息放在HashMap中 
  137.      * 所以需要遍历HashMap将数据组装成List 
  138.      *  
  139.      * @param mGruopMap 
  140.      * @return 
  141.      */  
  142.     private List<ImageBean> subGroupOfImage(HashMap<String, List<String>> mGruopMap){  
  143.         if(mGruopMap.size() == 0){  
  144.             return null;  
  145.         }  
  146.         List<ImageBean> list = new ArrayList<ImageBean>();  
  147.           
  148.         Iterator<Map.Entry<String, List<String>>> it = mGruopMap.entrySet().iterator();  
  149.         while (it.hasNext()) {  
  150.             Map.Entry<String, List<String>> entry = it.next();  
  151.             ImageBean mImageBean = new ImageBean();  
  152.             String key = entry.getKey();  
  153.             List<String> value = entry.getValue();  
  154.               
  155.             mImageBean.setFolderName(key);  
  156.             mImageBean.setImageCounts(value.size());  
  157.             mImageBean.setTopImagePath(value.get(0));//获取该组的第一张图片  
  158.               
  159.             list.add(mImageBean);  
  160.         }  
  161.           
  162.         return list;  
  163.           
  164.     }  
  165.   
  166.   
  167. }  
  • 首先看getImages()这个方法,该方法是使用ContentProvider将手机中的图片扫描出来,我这里只扫描了手机的外部存储中的图片,由于手机中可能存在很多的图片,扫描图片又比较耗时,所以我们在这里开启了子线程去获取图片,扫描的图片都存放在Cursor中,我们先要将图片按照文件夹进行分类,我们使用了HashMap来进行分类并将结果存储到mGruopMap(Key是文件夹名,Value是文件夹中的图片路径的List)中,分类完了关闭Cursor并利用Handler来通知主线程
  • 然后是subGroupOfImage()方法,改方法是将mGruopMap的数据组装到List中,在List中存放GridView中的每个item的数据对象ImageBean, 遍历HashMap对象,具体的逻辑看代码,之后就是给GridView设置Adapter。
  • 设置item点击事件,点击文件夹跳转到展示文件夹图片的Activity, 我们需要传递每个文件夹中的图片的路径的集合

看GroupAdapter的代码之前,我们先看一个比较重要的类,本地图片加载器NativeImageLoader

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.example.imagescan;  
  2.   
  3. import java.util.concurrent.ExecutorService;  
  4. import java.util.concurrent.Executors;  
  5.   
  6. import android.graphics.Bitmap;  
  7. import android.graphics.BitmapFactory;  
  8. import android.graphics.Point;  
  9. import android.os.Handler;  
  10. import android.os.Message;  
  11. import android.support.v4.util.LruCache;  
  12.   
  13. /** 
  14.  * 本地图片加载器,采用的是异步解析本地图片,单例模式利用getInstance()获取NativeImageLoader实例 
  15.  * 调用loadNativeImage()方法加载本地图片,此类可作为一个加载本地图片的工具类 
  16.  *  
  17.  * @blog http://blog.csdn.net/xiaanming 
  18.  *  
  19.  * @author xiaanming 
  20.  * 
  21.  */  
  22. public class NativeImageLoader {  
  23.     private LruCache<String, Bitmap> mMemoryCache;  
  24.     private static NativeImageLoader mInstance = new NativeImageLoader();  
  25.     private ExecutorService mImageThreadPool = Executors.newFixedThreadPool(1);  
  26.       
  27.       
  28.     private NativeImageLoader(){  
  29.         //获取应用程序的最大内存  
  30.         final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
  31.   
  32.         //用最大内存的1/4来存储图片  
  33.         final int cacheSize = maxMemory / 4;  
  34.         mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {  
  35.               
  36.             //获取每张图片的大小  
  37.             @Override  
  38.             protected int sizeOf(String key, Bitmap bitmap) {  
  39.                 return bitmap.getRowBytes() * bitmap.getHeight() / 1024;  
  40.             }  
  41.         };  
  42.     }  
  43.       
  44.     /** 
  45.      * 通过此方法来获取NativeImageLoader的实例 
  46.      * @return 
  47.      */  
  48.     public static NativeImageLoader getInstance(){  
  49.         return mInstance;  
  50.     }  
  51.       
  52.       
  53.     /** 
  54.      * 加载本地图片,对图片不进行裁剪 
  55.      * @param path 
  56.      * @param mCallBack 
  57.      * @return 
  58.      */  
  59.     public Bitmap loadNativeImage(final String path, final NativeImageCallBack mCallBack){  
  60.         return this.loadNativeImage(path, null, mCallBack);  
  61.     }  
  62.       
  63.     /** 
  64.      * 此方法来加载本地图片,这里的mPoint是用来封装ImageView的宽和高,我们会根据ImageView控件的大小来裁剪Bitmap 
  65.      * 如果你不想裁剪图片,调用loadNativeImage(final String path, final NativeImageCallBack mCallBack)来加载 
  66.      * @param path 
  67.      * @param mPoint 
  68.      * @param mCallBack 
  69.      * @return 
  70.      */  
  71.     public Bitmap loadNativeImage(final String path, final Point mPoint, final NativeImageCallBack mCallBack){  
  72.         //先获取内存中的Bitmap  
  73.         Bitmap bitmap = getBitmapFromMemCache(path);  
  74.           
  75.         final Handler mHander = new Handler(){  
  76.   
  77.             @Override  
  78.             public void handleMessage(Message msg) {  
  79.                 super.handleMessage(msg);  
  80.                 mCallBack.onImageLoader((Bitmap)msg.obj, path);  
  81.             }  
  82.               
  83.         };  
  84.           
  85.         //若该Bitmap不在内存缓存中,则启用线程去加载本地的图片,并将Bitmap加入到mMemoryCache中  
  86.         if(bitmap == null){  
  87.             mImageThreadPool.execute(new Runnable() {  
  88.                   
  89.                 @Override  
  90.                 public void run() {  
  91.                     //先获取图片的缩略图  
  92.                     Bitmap mBitmap = decodeThumbBitmapForFile(path, mPoint == null ? 0: mPoint.x, mPoint == null ? 0: mPoint.y);  
  93.                     Message msg = mHander.obtainMessage();  
  94.                     msg.obj = mBitmap;  
  95.                     mHander.sendMessage(msg);  
  96.                       
  97.                     //将图片加入到内存缓存  
  98.                     addBitmapToMemoryCache(path, mBitmap);  
  99.                 }  
  100.             });  
  101.         }  
  102.         return bitmap;  
  103.           
  104.     }  
  105.   
  106.       
  107.       
  108.     /** 
  109.      * 往内存缓存中添加Bitmap 
  110.      *  
  111.      * @param key 
  112.      * @param bitmap 
  113.      */  
  114.     private void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
  115.         if (getBitmapFromMemCache(key) == null && bitmap != null) {  
  116.             mMemoryCache.put(key, bitmap);  
  117.         }  
  118.     }  
  119.   
  120.     /** 
  121.      * 根据key来获取内存中的图片 
  122.      * @param key 
  123.      * @return 
  124.      */  
  125.     private Bitmap getBitmapFromMemCache(String key) {  
  126.         return mMemoryCache.get(key);  
  127.     }  
  128.       
  129.       
  130.     /** 
  131.      * 根据View(主要是ImageView)的宽和高来获取图片的缩略图 
  132.      * @param path 
  133.      * @param viewWidth 
  134.      * @param viewHeight 
  135.      * @return 
  136.      */  
  137.     private Bitmap decodeThumbBitmapForFile(String path, int viewWidth, int viewHeight){  
  138.         BitmapFactory.Options options = new BitmapFactory.Options();  
  139.         //设置为true,表示解析Bitmap对象,该对象不占内存  
  140.         options.inJustDecodeBounds = true;  
  141.         BitmapFactory.decodeFile(path, options);  
  142.         //设置缩放比例  
  143.         options.inSampleSize = computeScale(options, viewWidth, viewHeight);  
  144.           
  145.         //设置为false,解析Bitmap对象加入到内存中  
  146.         options.inJustDecodeBounds = false;  
  147.           
  148.         return BitmapFactory.decodeFile(path, options);  
  149.     }  
  150.       
  151.       
  152.     /** 
  153.      * 根据View(主要是ImageView)的宽和高来计算Bitmap缩放比例。默认不缩放 
  154.      * @param options 
  155.      * @param width 
  156.      * @param height 
  157.      */  
  158.     private int computeScale(BitmapFactory.Options options, int viewWidth, int viewHeight){  
  159.         int inSampleSize = 1;  
  160.         if(viewWidth == 0 || viewWidth == 0){  
  161.             return inSampleSize;  
  162.         }  
  163.         int bitmapWidth = options.outWidth;  
  164.         int bitmapHeight = options.outHeight;  
  165.           
  166.         //假如Bitmap的宽度或高度大于我们设定图片的View的宽高,则计算缩放比例  
  167.         if(bitmapWidth > viewWidth || bitmapHeight > viewWidth){  
  168.             int widthScale = Math.round((float) bitmapWidth / (float) viewWidth);  
  169.             int heightScale = Math.round((float) bitmapHeight / (float) viewWidth);  
  170.               
  171.             //为了保证图片不缩放变形,我们取宽高比例最小的那个  
  172.             inSampleSize = widthScale < heightScale ? widthScale : heightScale;  
  173.         }  
  174.         return inSampleSize;  
  175.     }  
  176.       
  177.       
  178.     /** 
  179.      * 加载本地图片的回调接口 
  180.      *  
  181.      * @author xiaanming 
  182.      * 
  183.      */  
  184.     public interface NativeImageCallBack{  
  185.         /** 
  186.          * 当子线程加载完了本地的图片,将Bitmap和图片路径回调在此方法中 
  187.          * @param bitmap 
  188.          * @param path 
  189.          */  
  190.         public void onImageLoader(Bitmap bitmap, String path);  
  191.     }  
  192. }  

该类是一个单例类,提供了本地图片加载,内存缓存,裁剪等逻辑,该类在加载本地图片的时候采用的是异步加载的方式,对于大图片的加载也是比较耗时的,所以采用子线程的方式去加载,对于图片的缓存机制使用的是LruCache,使用手机分配给应用程序内存的1/4用来缓存图片,除了使用LruCache缓存图片之外,还对图片进行了裁剪,举个很简单的例子,假如我们的控件大小是100 * 100, 而我们的图片是400*400,我们加载这么大的图片需要很多的内存,所以我们采用了图片裁剪,根据控件的大小来确定图片的裁剪比例,从而减小内存的消耗,提高GridView滑动的流畅度,介绍里面几个比较重要的方法
  1. computeScale()计算图片需要裁剪的比例,根据控件的大小和图片的大小确定比例,如果图片比控件大,我们就进行裁剪,否则不需要。
  2. decodeThumbBitmapForFile()方法是根据计算好了图片裁剪的比例之后从文件中加载图片,我们先设置options.inJustDecodeBounds = true表示解析不占用内存,但是我们能获取图片的具体大小,利用computeScale()计算好比例,在将options.inJustDecodeBounds=false,再次解析Bitmap,这样子就对图片进行了裁剪。
  3. loadNativeImage(final String path, final Point mPoint, final NativeImageCallBack mCallBack)我们在客户端只需要调用该方法就能获取到Bitmap对象,里面的具体逻辑是先判断内存缓存LruCache中是否存在该Bitmap,不存在就开启子线程去读取,为了方便管理加载本地图片线程,这里使用了线程池,池中只能容纳一个线程,读取完了本地图片先将Bitmap加入到LruCache中,保存的Key为图片路径,然后再使用Handler通知主线程图片加载好了,之后将Bitmap和路径回调到方法onImageLoader(Bitmap bitmap, String path)中,该方法的mPoint是用来封装控件的宽和高的对象
  4. 如果不对图片进行裁剪直接这个方法的重载方法loadNativeImage(final String path, final NativeImageCallBack mCallBack) 就行了,逻辑是一样的,只是这个方法不对图片进行裁剪

接下来就是GridView的Adapter类的代码

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.example.imagescan;  
  2.   
  3. import java.util.List;  
  4.   
  5. import android.content.Context;  
  6. import android.graphics.Bitmap;  
  7. import android.graphics.Point;  
  8. import android.view.LayoutInflater;  
  9. import android.view.View;  
  10. import android.view.ViewGroup;  
  11. import android.widget.BaseAdapter;  
  12. import android.widget.GridView;  
  13. import android.widget.ImageView;  
  14. import android.widget.TextView;  
  15.   
  16. import com.example.imagescan.MyImageView.OnMeasureListener;  
  17. import com.example.imagescan.NativeImageLoader.NativeImageCallBack;  
  18.   
  19. public class GroupAdapter extends BaseAdapter{  
  20.     private List<ImageBean> list;  
  21.     private Point mPoint = new Point(00);//用来封装ImageView的宽和高的对象  
  22.     private GridView mGridView;  
  23.     protected LayoutInflater mInflater;  
  24.       
  25.     @Override  
  26.     public int getCount() {  
  27.         return list.size();  
  28.     }  
  29.   
  30.     @Override  
  31.     public Object getItem(int position) {  
  32.         return list.get(position);  
  33.     }  
  34.   
  35.   
  36.     @Override  
  37.     public long getItemId(int position) {  
  38.         return position;  
  39.     }  
  40.       
  41.     public GroupAdapter(Context context, List<ImageBean> list, GridView mGridView){  
  42.         this.list = list;  
  43.         this.mGridView = mGridView;  
  44.         mInflater = LayoutInflater.from(context);  
  45.     }  
  46.       
  47.   
  48.     @Override  
  49.     public View getView(int position, View convertView, ViewGroup parent) {  
  50.         final ViewHolder viewHolder;  
  51.         ImageBean mImageBean = list.get(position);  
  52.         String path = mImageBean.getTopImagePath();  
  53.         if(convertView == null){  
  54.             viewHolder = new ViewHolder();  
  55.             convertView = mInflater.inflate(R.layout.grid_group_item, null);  
  56.             viewHolder.mImageView = (MyImageView) convertView.findViewById(R.id.group_image);  
  57.             viewHolder.mTextViewTitle = (TextView) convertView.findViewById(R.id.group_title);  
  58.             viewHolder.mTextViewCounts = (TextView) convertView.findViewById(R.id.group_count);  
  59.               
  60.             //用来监听ImageView的宽和高  
  61.             viewHolder.mImageView.setOnMeasureListener(new OnMeasureListener() {  
  62.                   
  63.                 @Override  
  64.                 public void onMeasureSize(int width, int height) {  
  65.                     mPoint.set(width, height);  
  66.                 }  
  67.             });  
  68.               
  69.             convertView.setTag(viewHolder);  
  70.         }else{  
  71.             viewHolder = (ViewHolder) convertView.getTag();  
  72.             viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);  
  73.         }  
  74.           
  75.         viewHolder.mTextViewTitle.setText(mImageBean.getFolderName());  
  76.         viewHolder.mTextViewCounts.setText(Integer.toString(mImageBean.getImageCounts()));  
  77.         //给ImageView设置路径Tag,这是异步加载图片的小技巧  
  78.         viewHolder.mImageView.setTag(path);  
  79.           
  80.           
  81.         //利用NativeImageLoader类加载本地图片  
  82.         Bitmap bitmap = NativeImageLoader.getInstance().loadNativeImage(path, mPoint, new NativeImageCallBack() {  
  83.               
  84.             @Override  
  85.             public void onImageLoader(Bitmap bitmap, String path) {  
  86.                 ImageView mImageView = (ImageView) mGridView.findViewWithTag(path);  
  87.                 if(bitmap != null && mImageView != null){  
  88.                     mImageView.setImageBitmap(bitmap);  
  89.                 }  
  90.             }  
  91.         });  
  92.           
  93.         if(bitmap != null){  
  94.             viewHolder.mImageView.setImageBitmap(bitmap);  
  95.         }else{  
  96.             viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);  
  97.         }  
  98.           
  99.           
  100.         return convertView;  
  101.     }  
  102.       
  103.       
  104.       
  105.     public static class ViewHolder{  
  106.         public MyImageView mImageView;  
  107.         public TextView mTextViewTitle;  
  108.         public TextView mTextViewCounts;  
  109.     }  
  110.   
  111.       
  112. }  

首先我们将每个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的大小来裁剪图片

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.example.imagescan;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.widget.ImageView;  
  6.   
  7. public class MyImageView extends ImageView {  
  8.     private OnMeasureListener onMeasureListener;  
  9.       
  10.     public void setOnMeasureListener(OnMeasureListener onMeasureListener) {  
  11.         this.onMeasureListener = onMeasureListener;  
  12.     }  
  13.   
  14.     public MyImageView(Context context, AttributeSet attrs) {  
  15.         super(context, attrs);  
  16.     }  
  17.   
  18.     public MyImageView(Context context, AttributeSet attrs, int defStyle) {  
  19.         super(context, attrs, defStyle);  
  20.     }  
  21.   
  22.     @Override  
  23.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  24.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  25.           
  26.         //将图片测量的大小回调到onMeasureSize()方法中  
  27.         if(onMeasureListener != null){  
  28.             onMeasureListener.onMeasureSize(getMeasuredWidth(), getMeasuredHeight());  
  29.         }  
  30.     }  
  31.   
  32.     public interface OnMeasureListener{  
  33.         public void onMeasureSize(int width, int height);  
  34.     }  
  35.       
  36. }  
上面这些代码就完成了第一个界面的功能了,接下来就是点击GridView的item跳转另一个界面来显示该文件夹下面的所有图片,功能跟第一个界面差不多,也是使用GridView来显示图片,第二个界面的布局代码我就不贴了,直接贴上界面的代码

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.example.imagescan;  
  2.   
  3. import java.util.List;  
  4.   
  5. import android.app.Activity;  
  6. import android.os.Bundle;  
  7. import android.widget.GridView;  
  8. import android.widget.Toast;  
  9.   
  10. public class ShowImageActivity extends Activity {  
  11.     private GridView mGridView;  
  12.     private List<String> list;  
  13.     private ChildAdapter adapter;  
  14.   
  15.     @Override  
  16.     protected void onCreate(Bundle savedInstanceState) {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.show_image_activity);  
  19.           
  20.         mGridView = (GridView) findViewById(R.id.child_grid);  
  21.         list = getIntent().getStringArrayListExtra("data");  
  22.           
  23.         adapter = new ChildAdapter(this, list, mGridView);  
  24.         mGridView.setAdapter(adapter);  
  25.           
  26.     }  
  27.   
  28.     @Override  
  29.     public void onBackPressed() {  
  30.         Toast.makeText(this"选中 " + adapter.getSelectItems().size() + " item", Toast.LENGTH_LONG).show();  
  31.         super.onBackPressed();  
  32.     }  
  33.       
  34.       
  35. }  
GridView的item上面一个我们自定义的MyImageView用来显示图片,另外还有一个CheckBox来记录我们选中情况,Adapter的代码如下
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.example.imagescan;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.HashMap;  
  5. import java.util.Iterator;  
  6. import java.util.List;  
  7. import java.util.Map;  
  8.   
  9. import android.content.Context;  
  10. import android.graphics.Bitmap;  
  11. import android.graphics.Point;  
  12. import android.view.LayoutInflater;  
  13. import android.view.View;  
  14. import android.view.ViewGroup;  
  15. import android.widget.BaseAdapter;  
  16. import android.widget.CheckBox;  
  17. import android.widget.CompoundButton;  
  18. import android.widget.ImageView;  
  19. import android.widget.CompoundButton.OnCheckedChangeListener;  
  20. import android.widget.GridView;  
  21.   
  22. import com.example.imagescan.MyImageView.OnMeasureListener;  
  23. import com.example.imagescan.NativeImageLoader.NativeImageCallBack;  
  24. import com.nineoldandroids.animation.AnimatorSet;  
  25. import com.nineoldandroids.animation.ObjectAnimator;  
  26.   
  27. public class ChildAdapter extends BaseAdapter {  
  28.     private Point mPoint = new Point(00);//用来封装ImageView的宽和高的对象  
  29.     /** 
  30.      * 用来存储图片的选中情况 
  31.      */  
  32.     private HashMap<Integer, Boolean> mSelectMap = new HashMap<Integer, Boolean>();  
  33.     private GridView mGridView;  
  34.     private List<String> list;  
  35.     protected LayoutInflater mInflater;  
  36.   
  37.     public ChildAdapter(Context context, List<String> list, GridView mGridView) {  
  38.         this.list = list;  
  39.         this.mGridView = mGridView;  
  40.         mInflater = LayoutInflater.from(context);  
  41.     }  
  42.       
  43.     @Override  
  44.     public int getCount() {  
  45.         return list.size();  
  46.     }  
  47.   
  48.     @Override  
  49.     public Object getItem(int position) {  
  50.         return list.get(position);  
  51.     }  
  52.   
  53.   
  54.     @Override  
  55.     public long getItemId(int position) {  
  56.         return position;  
  57.     }  
  58.       
  59.     @Override  
  60.     public View getView(final int position, View convertView, ViewGroup parent) {  
  61.         final ViewHolder viewHolder;  
  62.         String path = list.get(position);  
  63.           
  64.         if(convertView == null){  
  65.             convertView = mInflater.inflate(R.layout.grid_child_item, null);  
  66.             viewHolder = new ViewHolder();  
  67.             viewHolder.mImageView = (MyImageView) convertView.findViewById(R.id.child_image);  
  68.             viewHolder.mCheckBox = (CheckBox) convertView.findViewById(R.id.child_checkbox);  
  69.               
  70.             //用来监听ImageView的宽和高  
  71.             viewHolder.mImageView.setOnMeasureListener(new OnMeasureListener() {  
  72.                   
  73.                 @Override  
  74.                 public void onMeasureSize(int width, int height) {  
  75.                     mPoint.set(width, height);  
  76.                 }  
  77.             });  
  78.               
  79.             convertView.setTag(viewHolder);  
  80.         }else{  
  81.             viewHolder = (ViewHolder) convertView.getTag();  
  82.             viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);  
  83.         }  
  84.         viewHolder.mImageView.setTag(path);  
  85.         viewHolder.mCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {  
  86.               
  87.             @Override  
  88.             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {  
  89.                 //如果是未选中的CheckBox,则添加动画  
  90.                 if(!mSelectMap.containsKey(position) || !mSelectMap.get(position)){  
  91.                     addAnimation(viewHolder.mCheckBox);  
  92.                 }  
  93.                 mSelectMap.put(position, isChecked);  
  94.             }  
  95.         });  
  96.           
  97.         viewHolder.mCheckBox.setChecked(mSelectMap.containsKey(position) ? mSelectMap.get(position) : false);  
  98.           
  99.         //利用NativeImageLoader类加载本地图片  
  100.         Bitmap bitmap = NativeImageLoader.getInstance().loadNativeImage(path, mPoint, new NativeImageCallBack() {  
  101.               
  102.             @Override  
  103.             public void onImageLoader(Bitmap bitmap, String path) {  
  104.                 ImageView mImageView = (ImageView) mGridView.findViewWithTag(path);  
  105.                 if(bitmap != null && mImageView != null){  
  106.                     mImageView.setImageBitmap(bitmap);  
  107.                 }  
  108.             }  
  109.         });  
  110.           
  111.         if(bitmap != null){  
  112.             viewHolder.mImageView.setImageBitmap(bitmap);  
  113.         }else{  
  114.             viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);  
  115.         }  
  116.           
  117.         return convertView;  
  118.     }  
  119.       
  120.     /** 
  121.      * 给CheckBox加点击动画,利用开源库nineoldandroids设置动画  
  122.      * @param view 
  123.      */  
  124.     private void addAnimation(View view){  
  125.         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};  
  126.         AnimatorSet set = new AnimatorSet();  
  127.         set.playTogether(ObjectAnimator.ofFloat(view, "scaleX", vaules),   
  128.                 ObjectAnimator.ofFloat(view, "scaleY", vaules));  
  129.                 set.setDuration(150);  
  130.         set.start();  
  131.     }  
  132.       
  133.       
  134.     /** 
  135.      * 获取选中的Item的position 
  136.      * @return 
  137.      */  
  138.     public List<Integer> getSelectItems(){  
  139.         List<Integer> list = new ArrayList<Integer>();  
  140.         for(Iterator<Map.Entry<Integer, Boolean>> it = mSelectMap.entrySet().iterator(); it.hasNext();){  
  141.             Map.Entry<Integer, Boolean> entry = it.next();  
  142.             if(entry.getValue()){  
  143.                 list.add(entry.getKey());  
  144.             }  
  145.         }  
  146.           
  147.         return list;  
  148.     }  
  149.       
  150.       
  151.     public static class ViewHolder{  
  152.         public MyImageView mImageView;  
  153.         public CheckBox mCheckBox;  
  154.     }  
  155.   
  156.   
  157. }  

第二个界面的Adapter跟第一个界面差不多,无非多了一个CheckBox用来记录图片选择情况,我们只需要对CheckBox设置setOnCheckedChangeListener监听,微信的选中之后CheckBox有一个动画效果,所以我利用nineoldandroids动画库也给CheckBox加了一个动画效果,直接调用addAnimation()方法就能添加了,getSelectItems()方法就能获取我们选中的item的position了,知道了选中的position,其他的信息就都知道了,微信有对图片进行预览的功能,我这里就不添加了,如果有这个需求可以自行添加,给大家推荐一个https://github.com/chrisbanes/PhotoView


运行项目,效果如下




看起来还不错吧,采用的是异步读取图片,对图片进行了缓存和裁剪,使得在显示本地图片方面比较流畅,GridView滑动也挺流畅的,也有效的避免OOM的产生,工程中有些东西还没有贴完全,有兴趣的朋友可以下载Demo来运行一下,好了,今天的讲解到这里结束了,感谢大家观看,有疑问的朋友可以在下面留言,我会为大家解答的!

项目源码,点击下载
相关文章
|
2月前
|
Web App开发 缓存 前端开发
拿下奇怪的前端报错(六):多摄手机webrtc拉取视频流会导致应用崩溃,从而无法进行人像扫描
本文介绍了一种解决手机摄像头切换导致应用崩溃的问题的方法。针对不支持facingMode配置的四摄手机,通过缓存和序号切换的方式,确保应用在特定设备上不会频繁崩溃,提升用户体验。
|
3月前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
71 20
Android经典面试题之图片Bitmap怎么做优化
|
2月前
|
移动开发 前端开发 Android开发
开发指南059-App实现微信扫描登录
App是用uniapp开发的,打包为apk,上传到安卓平板中使用
|
5月前
|
小程序 开发者
【微信小程序-原生开发】实用教程05-首页(含自定义调试模式、插入图片、图文排版、底部留白、添加本地图片)
【微信小程序-原生开发】实用教程05-首页(含自定义调试模式、插入图片、图文排版、底部留白、添加本地图片)
61 0
|
2月前
|
小程序 JavaScript API
微信小程序开发之:保存图片到手机,使用uni-app 开发小程序;还有微信原生保存图片到手机
这篇文章介绍了如何在uni-app和微信小程序中实现将图片保存到用户手机相册的功能。
767 0
微信小程序开发之:保存图片到手机,使用uni-app 开发小程序;还有微信原生保存图片到手机
|
2月前
|
算法 小程序 Java
java制作海报三:获取微信二维码详情,并改变大小,合成到海报(另一张图片)上
这篇文章介绍了如何使用Java获取微信小程序的二维码,并将其调整大小后合成到海报(另一张图片)上。
44 0
|
3月前
|
vr&ar 图形学 UED
电子沙盘VR模型大屏平板手机微信使用方案
数字孪生电子沙盘和VR模型被广泛应用在房地产等行业,为不同设备定制不同版本的模型是常见做法。然而,通过实时云渲染技术,可以将PC端的VR模型转化为网页版,使用户能够在平板或手机上流畅浏览详细信息,无需开发多个版本。这不仅提升了用户体验,还简化了模型提供商的工作流程,降低了成本。尤其在新楼盘发布时,可通过公众号或广告链接快速吸引潜在客户。成本主要取决于并发用户数及显卡性能要求,但该技术显著提高了跨设备访问的便利性。
52 1
|
4月前
|
小程序 前端开发
|
4月前
|
数据处理 开发工具 数据安全/隐私保护
Android平台RTMP推送|轻量级RTSP服务|GB28181接入之文字、png图片水印的精进之路
本文探讨了Android平台上推流模块中添加文字与PNG水印的技术演进。自2015年起,为了满足应急指挥及安防领域的需求,逐步发展出三代水印技术:第一代为静态文字与图像水印;第二代实现了动态更新水印内容的能力,例如实时位置与时间信息;至第三代,则优化了数据传输效率,直接使用Bitmap对象传递水印数据至JNI层,减少了内存拷贝次数。这些迭代不仅提升了用户体验和技术效率,也体现了开发者追求极致与不断创新的精神。
|
4月前
|
开发工具 Android开发