版权声明:本文为博主原创文章,未经博主允许不得转载。

转载请注明本文出自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来封装这三个属性

  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

  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张图片添加的效果,其实不是,后面的叠加效果只是一张背景图片而已,代码先贴上来

  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的作用,待会再给大家说,我们继续看代码

第一个界面的主要代码

  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. * 利用ContentProvider扫描手机中的图片,此方法在运行在子线程中
  84. */
  85. private void getImages() {
  86. //显示进度条
  87. mProgressDialog = ProgressDialog.show(this, null, "正在加载...");
  88.  
  89. new Thread(new Runnable() {
  90.  
  91. @Override
  92. public void run() {
  93. Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
  94. ContentResolver mContentResolver = MainActivity.this.getContentResolver();
  95.  
  96. //只查询jpeg和png的图片
  97. Cursor mCursor = mContentResolver.query(mImageUri, null,
  98. MediaStore.Images.Media.MIME_TYPE + "=? or "
  99. + MediaStore.Images.Media.MIME_TYPE + "=?",
  100. new String[] { "image/jpeg", "image/png" }, MediaStore.Images.Media.DATE_MODIFIED);
  101.  
  102. if(mCursor == null){
  103. return;
  104. }
  105.  
  106. while (mCursor.moveToNext()) {
  107. //获取图片的路径
  108. String path = mCursor.getString(mCursor
  109. .getColumnIndex(MediaStore.Images.Media.DATA));
  110.  
  111. //获取该图片的父路径名
  112. String parentName = new File(path).getParentFile().getName();
  113.  
  114. //根据父路径名将图片放入到mGruopMap中
  115. if (!mGruopMap.containsKey(parentName)) {
  116. List<String> chileList = new ArrayList<String>();
  117. chileList.add(path);
  118. mGruopMap.put(parentName, chileList);
  119. } else {
  120. mGruopMap.get(parentName).add(path);
  121. }
  122. }
  123.  
  124. //通知Handler扫描图片完成
  125. mHandler.sendEmptyMessage(SCAN_OK);
  126. mCursor.close();
  127. }
  128. }).start();
  129.  
  130. }
  131.  
  132. /**
  133. * 组装分组界面GridView的数据源,因为我们扫描手机的时候将图片信息放在HashMap中
  134. * 所以需要遍历HashMap将数据组装成List
  135. *
  136. * @param mGruopMap
  137. * @return
  138. */
  139. private List<ImageBean> subGroupOfImage(HashMap<String, List<String>> mGruopMap){
  140. if(mGruopMap.size() == 0){
  141. return null;
  142. }
  143. List<ImageBean> list = new ArrayList<ImageBean>();
  144.  
  145. Iterator<Map.Entry<String, List<String>>> it = mGruopMap.entrySet().iterator();
  146. while (it.hasNext()) {
  147. Map.Entry<String, List<String>> entry = it.next();
  148. ImageBean mImageBean = new ImageBean();
  149. String key = entry.getKey();
  150. List<String> value = entry.getValue();
  151.  
  152. mImageBean.setFolderName(key);
  153. mImageBean.setImageCounts(value.size());
  154. mImageBean.setTopImagePath(value.get(0));//获取该组的第一张图片
  155.  
  156. list.add(mImageBean);
  157. }
  158.  
  159. return list;
  160.  
  161. }
  162.  
  163. }
  • 首先看getImages()这个方法,该方法是使用ContentProvider将手机中的图片扫描出来,我这里只扫描了手机的外部存储中的图片,由于手机中可能存在很多的图片,扫描图片又比较耗时,所以我们在这里开启了子线程去获取图片,扫描的图片都存放在Cursor中,我们先要将图片按照文件夹进行分类,我们使用了HashMap来进行分类并将结果存储到mGruopMap(Key是文件夹名,Value是文件夹中的图片路径的List)中,分类完了关闭Cursor并利用Handler来通知主线程
  • 然后是subGroupOfImage()方法,改方法是将mGruopMap的数据组装到List中,在List中存放GridView中的每个item的数据对象ImageBean, 遍历HashMap对象,具体的逻辑看代码,之后就是给GridView设置Adapter。
  • 设置item点击事件,点击文件夹跳转到展示文件夹图片的Activity, 我们需要传递每个文件夹中的图片的路径的集合

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

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

该类是一个单例类,提供了本地图片加载,内存缓存,裁剪等逻辑,该类在加载本地图片的时候采用的是异步加载的方式,对于大图片的加载也是比较耗时的,所以采用子线程的方式去加载,对于图片的缓存机制使用的是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类的代码

  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(0, 0);//用来封装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. @Override
  36. public long getItemId(int position) {
  37. return position;
  38. }
  39.  
  40. public GroupAdapter(Context context, List<ImageBean> list, GridView mGridView){
  41. this.list = list;
  42. this.mGridView = mGridView;
  43. mInflater = LayoutInflater.from(context);
  44. }
  45.  
  46. @Override
  47. public View getView(int position, View convertView, ViewGroup parent) {
  48. final ViewHolder viewHolder;
  49. ImageBean mImageBean = list.get(position);
  50. String path = mImageBean.getTopImagePath();
  51. if(convertView == null){
  52. viewHolder = new ViewHolder();
  53. convertView = mInflater.inflate(R.layout.grid_group_item, null);
  54. viewHolder.mImageView = (MyImageView) convertView.findViewById(R.id.group_image);
  55. viewHolder.mTextViewTitle = (TextView) convertView.findViewById(R.id.group_title);
  56. viewHolder.mTextViewCounts = (TextView) convertView.findViewById(R.id.group_count);
  57.  
  58. //用来监听ImageView的宽和高
  59. viewHolder.mImageView.setOnMeasureListener(new OnMeasureListener() {
  60.  
  61. @Override
  62. public void onMeasureSize(int width, int height) {
  63. mPoint.set(width, height);
  64. }
  65. });
  66.  
  67. convertView.setTag(viewHolder);
  68. }else{
  69. viewHolder = (ViewHolder) convertView.getTag();
  70. viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);
  71. }
  72.  
  73. viewHolder.mTextViewTitle.setText(mImageBean.getFolderName());
  74. viewHolder.mTextViewCounts.setText(Integer.toString(mImageBean.getImageCounts()));
  75. //给ImageView设置路径Tag,这是异步加载图片的小技巧
  76. viewHolder.mImageView.setTag(path);
  77.  
  78. //利用NativeImageLoader类加载本地图片
  79. Bitmap bitmap = NativeImageLoader.getInstance().loadNativeImage(path, mPoint, new NativeImageCallBack() {
  80.  
  81. @Override
  82. public void onImageLoader(Bitmap bitmap, String path) {
  83. ImageView mImageView = (ImageView) mGridView.findViewWithTag(path);
  84. if(bitmap != null && mImageView != null){
  85. mImageView.setImageBitmap(bitmap);
  86. }
  87. }
  88. });
  89.  
  90. if(bitmap != null){
  91. viewHolder.mImageView.setImageBitmap(bitmap);
  92. }else{
  93. viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);
  94. }
  95.  
  96. return convertView;
  97. }
  98.  
  99. public static class ViewHolder{
  100. public MyImageView mImageView;
  101. public TextView mTextViewTitle;
  102. public TextView mTextViewCounts;
  103. }
  104.  
  105. }

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

  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来显示图片,第二个界面的布局代码我就不贴了,直接贴上界面的代码

  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. }

GridView的item上面一个我们自定义的MyImageView用来显示图片,另外还有一个CheckBox来记录我们选中情况,Adapter的代码如下

  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(0, 0);//用来封装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. @Override
  54. public long getItemId(int position) {
  55. return position;
  56. }
  57.  
  58. @Override
  59. public View getView(final int position, View convertView, ViewGroup parent) {
  60. final ViewHolder viewHolder;
  61. String path = list.get(position);
  62.  
  63. if(convertView == null){
  64. convertView = mInflater.inflate(R.layout.grid_child_item, null);
  65. viewHolder = new ViewHolder();
  66. viewHolder.mImageView = (MyImageView) convertView.findViewById(R.id.child_image);
  67. viewHolder.mCheckBox = (CheckBox) convertView.findViewById(R.id.child_checkbox);
  68.  
  69. //用来监听ImageView的宽和高
  70. viewHolder.mImageView.setOnMeasureListener(new OnMeasureListener() {
  71.  
  72. @Override
  73. public void onMeasureSize(int width, int height) {
  74. mPoint.set(width, height);
  75. }
  76. });
  77.  
  78. convertView.setTag(viewHolder);
  79. }else{
  80. viewHolder = (ViewHolder) convertView.getTag();
  81. viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);
  82. }
  83. viewHolder.mImageView.setTag(path);
  84. viewHolder.mCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
  85.  
  86. @Override
  87. public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
  88. //如果是未选中的CheckBox,则添加动画
  89. if(!mSelectMap.containsKey(position) || !mSelectMap.get(position)){
  90. addAnimation(viewHolder.mCheckBox);
  91. }
  92. mSelectMap.put(position, isChecked);
  93. }
  94. });
  95.  
  96. viewHolder.mCheckBox.setChecked(mSelectMap.containsKey(position) ? mSelectMap.get(position) : false);
  97.  
  98. //利用NativeImageLoader类加载本地图片
  99. Bitmap bitmap = NativeImageLoader.getInstance().loadNativeImage(path, mPoint, new NativeImageCallBack() {
  100.  
  101. @Override
  102. public void onImageLoader(Bitmap bitmap, String path) {
  103. ImageView mImageView = (ImageView) mGridView.findViewWithTag(path);
  104. if(bitmap != null && mImageView != null){
  105. mImageView.setImageBitmap(bitmap);
  106. }
  107. }
  108. });
  109.  
  110. if(bitmap != null){
  111. viewHolder.mImageView.setImageBitmap(bitmap);
  112. }else{
  113. viewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);
  114. }
  115.  
  116. return convertView;
  117. }
  118.  
  119. /**
  120. * 给CheckBox加点击动画,利用开源库nineoldandroids设置动画
  121. * @param view
  122. */
  123. private void addAnimation(View view){
  124. 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};
  125. AnimatorSet set = new AnimatorSet();
  126. set.playTogether(ObjectAnimator.ofFloat(view, "scaleX", vaules),
  127. ObjectAnimator.ofFloat(view, "scaleY", vaules));
  128. set.setDuration(150);
  129. set.start();
  130. }
  131.  
  132. /**
  133. * 获取选中的Item的position
  134. * @return
  135. */
  136. public List<Integer> getSelectItems(){
  137. List<Integer> list = new ArrayList<Integer>();
  138. for(Iterator<Map.Entry<Integer, Boolean>> it = mSelectMap.entrySet().iterator(); it.hasNext();){
  139. Map.Entry<Integer, Boolean> entry = it.next();
  140. if(entry.getValue()){
  141. list.add(entry.getKey());
  142. }
  143. }
  144.  
  145. return list;
  146. }
  147.  
  148. public static class ViewHolder{
  149. public MyImageView mImageView;
  150. public CheckBox mCheckBox;
  151. }
  152.  
  153. }

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

运行项目,效果如下

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

项目源码,点击下载

Android 使用ContentProvider扫描手机中的图片,仿微信显示本地图片效果的更多相关文章

  1. android高仿微信UI点击头像显示大图片效果, Android 使用ContentProvider扫描手机中的图片,仿微信显示本地图片效果

    http://www.cnblogs.com/Jaylong/archive/2012/09/27/androidUI.html http://blog.csdn.net/xiaanming/arti ...

  2. android 在 ListView 的 item 中插入 GridView 仿微信朋友圈图片显示。

    转载请声明出处(http://www.cnblogs.com/linguanh/) 先上张效果图: 1,思路简述 这个肯定是要重写 baseAdapter的了,这里我分了两个数据适配器,一个是自定义的 ...

  3. Android 使用开源库StickyGridHeaders来实现带sections和headers的GridView显示本地图片效果

    大家好!过完年回来到现在差不多一个月没写文章了,一是觉得不知道写哪些方面的文章,没有好的题材来写,二是因为自己的一些私事给耽误了,所以过完年的第一篇文章到现在才发表出来,2014年我还是会继续在CSD ...

  4. Android ImageView显示本地图片

    Android ImageView 显示本地图片 布局文件 <?xml version="1.0" encoding="utf-8"?> <R ...

  5. 在InternetExplorer.Application中显示本地图片

    忘记了,喜欢一个人的感觉 Demon's Blog  »  程序设计  »  在InternetExplorer.Application中显示本地图片 « 对VBS效率的再思考——处理二进制数据 Wo ...

  6. Atitit. html 使用js显示本地图片的设计方案.doc

    Atitit. html 使用js显示本地图片的设计方案.doc 1.  Local mode  是可以的..web模式走有的不能兰.1 2. IE8.0 显示本地图片 img.src=本地图片路径无 ...

  7. Atitit. IE8.0 显示本地图片预览解决方案 img.src=本地图片路径无效的解决方案

    Atitit. IE8.0 显示本地图片预览解决方案 img.src=本地图片路径无效的解决方案 1. IE8.0 显示本地图片 img.src=本地图片路径无效的解决方案1 1.1. div来完成  ...

  8. Django Admin 图片路径设置显示为图片(imageField显示方法设置)

    一  使用环境 开发系统: windows IDE: pycharm 数据库: msyql,navicat 编程语言: python3.7  (Windows x86-64 executable in ...

  9. Slog71_选取、上传和显示本地图片GET !(微信小程序之云开发-全栈时代3)

    ArthurSlog SLog-71 Year·1 Guangzhou·China Sep 12th 2018 ArthurSlog Page GitHub NPM Package Page 掘金主页 ...

随机推荐

  1. poj1785 Binary Search Heap Construction

    此题可以先排序再用rmq递归解决. 当然可以用treap. http://poj.org/problem?id=1785 #include <cstdio> #include <cs ...

  2. Android GridView 第一个Item 点击没反应

    @Override public View getView(final int position, View convertView, ViewGroup parent) { final ViewHo ...

  3. Behavior Designer中的内置消息机制

    最近在用Behavior Designer,其中需要用到消息机制,看了一下其中自带了这套东西 注册 Owner.RegisterEvent<string>("Message&qu ...

  4. Poj(1273),最大流,EK

    Drainage Ditches Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 69355   Accepted: 2687 ...

  5. Servlet基础(下)

    10.Servlet定义初始化参数必须使用web.xml中的init-param/para-name和 para-value元素;11.ServletConfig对象的getInitParameter ...

  6. sql中decode(...)函数的用法

    相当于if语句 decode函数比较1个参数时 SELECT ID,DECODE(inParam,'beComparedParam','值1' ,'值2') name FROM bank #如果第一个 ...

  7. sql 百万级数据库优化方案

    转自http://blog.sina.com.cn/s/blog_724cd89d0100ppcz.html 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉 ...

  8. Linux按照CPU、内存、磁盘IO、网络性能监测

      系统优化是一项复杂.繁琐.长期的工作,优化前需要监测.采集.测试.评估,优化后也需要测试.采集.评估.监测,而且是一个长期和持续的过程,不 是说现在优化了,测试了,以后就可以一劳永逸了,也不是说书 ...

  9. 查看linux发行版本、内核版本命令

    查看linux发行版本: $lsb_release -a 查看linux内核版本: $cat /proc/version 或 $uname -a

  10. CodeForces 527B Error Correct System

    Error Correct System Time Limit:2000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I6 ...