转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/39943731,本文出自: 【张鸿洋的博客】

1、概述

关于手机图片加载器,在当今像素随随便便破千万的时代,一张图片占据的内存都相当可观,作为高大尚程序猿的我们,有必要掌握图片的压缩,缓存等处理,以到达纵使你有万张照片,纵使你的像素再高,我们也能正确的显示所有的图片。当然了,单纯显示图片没撒意思,我们决定高仿一下微信的图片选择器,在此,感谢微信!本篇博客将基于以下两篇博客:

Android 快速开发系列 打造万能的ListView GridView 适配器  将使用我们打造的CommonAdapter作为我们例子中GridView以及ListView的适配器

Android Handler 异步消息处理机制的妙用 创建强大的图片加载类 将使用我们自己写的ImageLoader作为我们的图片加载的核心类

如果你没看过也没关系,等看完本篇博客,可以结合以上两篇再进行充分理解一下。

好了,首先贴一下效果图:

动态图实在是录不出来,大家自己打开微信点击发表图片,或者聊天窗口发送图片,大致和微信的效果一样~

简单描述一下:

1、默认显示图片最多的文件夹图片,以及底部显示图片总数量;如上图1;

2、点击底部,弹出popupWindow,popupWindow包含所有含有图片的文件夹,以及显示每个文件夹中图片数量;如上图2;注:此时Activity变暗

3、选择任何文件夹,进入该文件夹图片显示,可以点击选择图片,当然了,点击已选择的图片则会取消选择;如上图3;注:选中图片变暗

当然了,最重要的效果一定流畅,不能动不动OOM~~

本人测试手机小米2s,图片6802张,未出现OOM异常,效果也是非常流畅,堪比图库~

不过存在bug在所难免,大家可以留言说下自己发现的bug;文末会提供源码下载。

好了,下面就可以代码的征程了~

2、图片的列表页

首先对手机中图片进行扫描,拿到图片数量最多的,直接显示在GridView上;并且扫描结束,得到一个所有包含图片的文件夹信息的List;

对于文件夹信息,我们单独创建了一个Bean:

[java] view plain copy print ?
  1. package com.zhy.bean;
  2. public class ImageFloder
  3. {
  4. /**
  5. * 图片的文件夹路径
  6. */
  7. private String dir;
  8. /**
  9. * 第一张图片的路径
  10. */
  11. private String firstImagePath;
  12. /**
  13. * 文件夹的名称
  14. */
  15. private String name;
  16. /**
  17. * 图片的数量
  18. */
  19. private int count;
  20. public String getDir()
  21. {
  22. return dir;
  23. }
  24. public void setDir(String dir)
  25. {
  26. this.dir = dir;
  27. int lastIndexOf = this.dir.lastIndexOf(“/”);
  28. this.name = this.dir.substring(lastIndexOf);
  29. }
  30. public String getFirstImagePath()
  31. {
  32. return firstImagePath;
  33. }
  34. public void setFirstImagePath(String firstImagePath)
  35. {
  36. this.firstImagePath = firstImagePath;
  37. }
  38. public String getName()
  39. {
  40. return name;
  41. }
  42. public int getCount()
  43. {
  44. return count;
  45. }
  46. public void setCount(int count)
  47. {
  48. this.count = count;
  49. }
  50. }
package com.zhy.bean;public class ImageFloder
{/*** 图片的文件夹路径*/private String dir;/*** 第一张图片的路径*/private String firstImagePath;/*** 文件夹的名称*/private String name;/*** 图片的数量*/private int count;public String getDir(){return dir;}public void setDir(String dir){this.dir = dir;int lastIndexOf = this.dir.lastIndexOf("/");this.name = this.dir.substring(lastIndexOf);}public String getFirstImagePath(){return firstImagePath;}public void setFirstImagePath(String firstImagePath){this.firstImagePath = firstImagePath;}public String getName(){return name;}public int getCount(){return count;}public void setCount(int count){this.count = count;}}

用来存储当前文件夹的路径,当前文件夹包含多少张图片,以及第一张图片路径用于做文件夹的图标;注:文件夹的名称,我们在set文件夹的路径的时候,自动提取,仔细看下setDir这个方法。

接下来就是扫描手机图片的代码了:

[java] view plain copy print ?
  1. @Override
  2. protected void onCreate(Bundle savedInstanceState)
  3. {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. DisplayMetrics outMetrics = new DisplayMetrics();
  7. getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
  8. mScreenHeight = outMetrics.heightPixels;
  9. initView();
  10. getImages();
  11. initEvent();
  12. }
  13. /**
  14. * 利用ContentProvider扫描手机中的图片,此方法在运行在子线程中 完成图片的扫描,最终获得jpg最多的那个文件夹
  15. */
  16. private void getImages()
  17. {
  18. if (!Environment.getExternalStorageState().equals(
  19. Environment.MEDIA_MOUNTED))
  20. {
  21. Toast.makeText(this, “暂无外部存储”, Toast.LENGTH_SHORT).show();
  22. return;
  23. }
  24. // 显示进度条
  25. mProgressDialog = ProgressDialog.show(this, null, “正在加载…”);
  26. new Thread(new Runnable()
  27. {
  28. @Override
  29. public void run()
  30. {
  31. String firstImage = null;
  32. Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
  33. ContentResolver mContentResolver = MainActivity.this
  34. .getContentResolver();
  35. // 只查询jpeg和png的图片
  36. Cursor mCursor = mContentResolver.query(mImageUri, null,
  37. MediaStore.Images.Media.MIME_TYPE + ”=? or ”
  38. + MediaStore.Images.Media.MIME_TYPE + ”=?”,
  39. new String[] { “image/jpeg”, “image/png” },
  40. MediaStore.Images.Media.DATE_MODIFIED);
  41. Log.e(”TAG”, mCursor.getCount() + “”);
  42. while (mCursor.moveToNext())
  43. {
  44. // 获取图片的路径
  45. String path = mCursor.getString(mCursor
  46. .getColumnIndex(MediaStore.Images.Media.DATA));
  47. Log.e(”TAG”, path);
  48. // 拿到第一张图片的路径
  49. if (firstImage == null)
  50. firstImage = path;
  51. // 获取该图片的父路径名
  52. File parentFile = new File(path).getParentFile();
  53. if (parentFile == null)
  54. continue;
  55. String dirPath = parentFile.getAbsolutePath();
  56. ImageFloder imageFloder = null;
  57. // 利用一个HashSet防止多次扫描同一个文件夹(不加这个判断,图片多起来还是相当恐怖的~~)
  58. if (mDirPaths.contains(dirPath))
  59. {
  60. continue;
  61. } else
  62. {
  63. mDirPaths.add(dirPath);
  64. // 初始化imageFloder
  65. imageFloder = new ImageFloder();
  66. imageFloder.setDir(dirPath);
  67. imageFloder.setFirstImagePath(path);
  68. }
  69. int picSize = parentFile.list(new FilenameFilter()
  70. {
  71. @Override
  72. public boolean accept(File dir, String filename)
  73. {
  74. if (filename.endsWith(“.jpg”)
  75. || filename.endsWith(”.png”)
  76. || filename.endsWith(”.jpeg”))
  77. return true;
  78. return false;
  79. }
  80. }).length;
  81. totalCount += picSize;
  82. imageFloder.setCount(picSize);
  83. mImageFloders.add(imageFloder);
  84. if (picSize > mPicsSize)
  85. {
  86. mPicsSize = picSize;
  87. mImgDir = parentFile;
  88. }
  89. }
  90. mCursor.close();
  91. // 扫描完成,辅助的HashSet也就可以释放内存了
  92. mDirPaths = null;
  93. // 通知Handler扫描图片完成
  94. mHandler.sendEmptyMessage(0x110);
  95. }
  96. }).start();
  97. }
@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);DisplayMetrics outMetrics = new DisplayMetrics();getWindowManager().getDefaultDisplay().getMetrics(outMetrics);mScreenHeight = outMetrics.heightPixels;initView();getImages();initEvent();}/*** 利用ContentProvider扫描手机中的图片,此方法在运行在子线程中 完成图片的扫描,最终获得jpg最多的那个文件夹*/private void getImages(){if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){Toast.makeText(this, "暂无外部存储", Toast.LENGTH_SHORT).show();return;}// 显示进度条mProgressDialog = ProgressDialog.show(this, null, "正在加载...");new Thread(new Runnable(){@Overridepublic void run(){String firstImage = null;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);Log.e("TAG", mCursor.getCount() + "");while (mCursor.moveToNext()){// 获取图片的路径String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA));Log.e("TAG", path);// 拿到第一张图片的路径if (firstImage == null)firstImage = path;// 获取该图片的父路径名File parentFile = new File(path).getParentFile();if (parentFile == null)continue;String dirPath = parentFile.getAbsolutePath();ImageFloder imageFloder = null;// 利用一个HashSet防止多次扫描同一个文件夹(不加这个判断,图片多起来还是相当恐怖的~~)if (mDirPaths.contains(dirPath)){continue;} else{mDirPaths.add(dirPath);// 初始化imageFloderimageFloder = new ImageFloder();imageFloder.setDir(dirPath);imageFloder.setFirstImagePath(path);}int picSize = parentFile.list(new FilenameFilter(){@Overridepublic boolean accept(File dir, String filename){if (filename.endsWith(".jpg")|| filename.endsWith(".png")|| filename.endsWith(".jpeg"))return true;return false;}}).length;totalCount += picSize;imageFloder.setCount(picSize);mImageFloders.add(imageFloder);if (picSize > mPicsSize){mPicsSize = picSize;mImgDir = parentFile;}}mCursor.close();// 扫描完成,辅助的HashSet也就可以释放内存了mDirPaths = null;// 通知Handler扫描图片完成mHandler.sendEmptyMessage(0x110);}}).start();}

ps:运行出现空指针的话,在81行的位置添加判断,if(parentFile.list()==null)continue , 切记~~~有些图片比较诡异~~;

initView就不看了,都是些findViewById;

getImages主要就是扫描图片的代码,我们开启了一个Thread进行扫描,扫描完成以后,我们得到了图片最多文件夹路径(mImgDir),手机中图片数量(totalCount);以及所有包含图片文件夹信息(mImageFloders)

然后我们通过handler发送消息,在handleMessage里面:

1、创建GridView的适配器,为我们的GridView设置适配器,显示图片;

2、有了mImageFloders,就可以创建我们的popupWindow了

看一眼我们的Handler

[java] view plain copy print ?
  1. private Handler mHandler = new Handler()
  2. {
  3. public void handleMessage(android.os.Message msg)
  4. {
  5. mProgressDialog.dismiss();
  6. //为View绑定数据
  7. data2View();
  8. //初始化展示文件夹的popupWindw
  9. initListDirPopupWindw();
  10. }
  11. };
private Handler mHandler = new Handler(){public void handleMessage(android.os.Message msg){mProgressDialog.dismiss();//为View绑定数据data2View();//初始化展示文件夹的popupWindwinitListDirPopupWindw();}};

可以看到分别干了上述的两件事:

[java] view plain copy print ?
  1. /**
  2. * 为View绑定数据
  3. */
  4. private void data2View()
  5. {
  6. if (mImgDir == null)
  7. {
  8. Toast.makeText(getApplicationContext(), ”擦,一张图片没扫描到”,
  9. Toast.LENGTH_SHORT).show();
  10. return;
  11. }
  12. mImgs = Arrays.asList(mImgDir.list());
  13. /**
  14. * 可以看到文件夹的路径和图片的路径分开保存,极大的减少了内存的消耗;
  15. */
  16. mAdapter = new MyAdapter(getApplicationContext(), mImgs,
  17. R.layout.grid_item, mImgDir.getAbsolutePath());
  18. mGirdView.setAdapter(mAdapter);
  19. mImageCount.setText(totalCount + ”张”);
  20. };
/*** 为View绑定数据*/private void data2View(){if (mImgDir == null){Toast.makeText(getApplicationContext(), "擦,一张图片没扫描到",Toast.LENGTH_SHORT).show();return;}mImgs = Arrays.asList(mImgDir.list());/*** 可以看到文件夹的路径和图片的路径分开保存,极大的减少了内存的消耗;*/mAdapter = new MyAdapter(getApplicationContext(), mImgs,R.layout.grid_item, mImgDir.getAbsolutePath());mGirdView.setAdapter(mAdapter);mImageCount.setText(totalCount + "张");};

data2View就是我们当前Activity上所有的View设置数据了。

看到这里还用到了一个Adapter,我们GridView的:

[java] view plain copy print ?
  1. package com.zhy.imageloader;
  2. import java.util.LinkedList;
  3. import java.util.List;
  4. import android.content.Context;
  5. import android.graphics.Color;
  6. import android.view.View;
  7. import android.view.View.OnClickListener;
  8. import android.widget.ImageView;
  9. import com.zhy.utils.CommonAdapter;
  10. public class MyAdapter extends CommonAdapter<String>
  11. {
  12. /**
  13. * 用户选择的图片,存储为图片的完整路径
  14. */
  15. public static List<String> mSelectedImage = new LinkedList<String>();
  16. /**
  17. * 文件夹路径
  18. */
  19. private String mDirPath;
  20. public MyAdapter(Context context, List<String> mDatas, int itemLayoutId,
  21. String dirPath)
  22. {
  23. super(context, mDatas, itemLayoutId);
  24. this.mDirPath = dirPath;
  25. }
  26. @Override
  27. public void convert(final com.zhy.utils.ViewHolder helper, final String item)
  28. {
  29. // 设置no_pic
  30. helper.setImageResource(R.id.id_item_image, R.drawable.pictures_no);
  31. // 设置no_selected
  32. helper.setImageResource(R.id.id_item_select,
  33. R.drawable.picture_unselected);
  34. // 设置图片
  35. helper.setImageByUrl(R.id.id_item_image, mDirPath + ”/” + item);
  36. final ImageView mImageView = helper.getView(R.id.id_item_image);
  37. final ImageView mSelect = helper.getView(R.id.id_item_select);
  38. mImageView.setColorFilter(null);
  39. // 设置ImageView的点击事件
  40. mImageView.setOnClickListener(new OnClickListener()
  41. {
  42. // 选择,则将图片变暗,反之则反之
  43. @Override
  44. public void onClick(View v)
  45. {
  46. // 已经选择过该图片
  47. if (mSelectedImage.contains(mDirPath + “/” + item))
  48. {
  49. mSelectedImage.remove(mDirPath + ”/” + item);
  50. mSelect.setImageResource(R.drawable.picture_unselected);
  51. mImageView.setColorFilter(null);
  52. } else
  53. // 未选择该图片
  54. {
  55. mSelectedImage.add(mDirPath + ”/” + item);
  56. mSelect.setImageResource(R.drawable.pictures_selected);
  57. mImageView.setColorFilter(Color.parseColor(”#77000000”));
  58. }
  59. }
  60. });
  61. /**
  62. * 已经选择过的图片,显示出选择过的效果
  63. */
  64. if (mSelectedImage.contains(mDirPath + “/” + item))
  65. {
  66. mSelect.setImageResource(R.drawable.pictures_selected);
  67. mImageView.setColorFilter(Color.parseColor(”#77000000”));
  68. }
  69. }
  70. }
package com.zhy.imageloader;import java.util.LinkedList;
import java.util.List;import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;import com.zhy.utils.CommonAdapter;public class MyAdapter extends CommonAdapter<String>
{/*** 用户选择的图片,存储为图片的完整路径*/public static List<String> mSelectedImage = new LinkedList<String>();/*** 文件夹路径*/private String mDirPath;public MyAdapter(Context context, List<String> mDatas, int itemLayoutId,String dirPath){super(context, mDatas, itemLayoutId);this.mDirPath = dirPath;}@Overridepublic void convert(final com.zhy.utils.ViewHolder helper, final String item){// 设置no_pichelper.setImageResource(R.id.id_item_image, R.drawable.pictures_no);// 设置no_selectedhelper.setImageResource(R.id.id_item_select,R.drawable.picture_unselected);// 设置图片helper.setImageByUrl(R.id.id_item_image, mDirPath + "/" + item);final ImageView mImageView = helper.getView(R.id.id_item_image);final ImageView mSelect = helper.getView(R.id.id_item_select);mImageView.setColorFilter(null);// 设置ImageView的点击事件mImageView.setOnClickListener(new OnClickListener(){// 选择,则将图片变暗,反之则反之@Overridepublic void onClick(View v){// 已经选择过该图片if (mSelectedImage.contains(mDirPath + "/" + item)){mSelectedImage.remove(mDirPath + "/" + item);mSelect.setImageResource(R.drawable.picture_unselected);mImageView.setColorFilter(null);} else// 未选择该图片{mSelectedImage.add(mDirPath + "/" + item);mSelect.setImageResource(R.drawable.pictures_selected);mImageView.setColorFilter(Color.parseColor("#77000000"));}}});/*** 已经选择过的图片,显示出选择过的效果*/if (mSelectedImage.contains(mDirPath + "/" + item)){mSelect.setImageResource(R.drawable.pictures_selected);mImageView.setColorFilter(Color.parseColor("#77000000"));}}
}

可以看到我们GridView的Adapter继承了我们的CommonAdapter,如果不知道CommonAdapter为何物,可以去看看万能适配器那篇博文;

我们现在只需要实现convert方法:

在convert中,我们设置图片,设置事件等,对于图片的变暗,我们使用的是ImageView的setColorFilter ;根据Url加载图片的操作封装在helper.setImageByUrl(view,url)中,内部使用的是我们自己定义的ImageLoader,包括错乱处理都已经封装了,图片策略我们使用的是LIFO后进先出;不清楚的可以看文章一开始说明的那两篇博文,对于CommonAdapter以及ImageLoader都有从无到有的详细打造过程;

到此我们的第一个Activity的所有的任务就完成了~~~

3、展现文件夹的PopupWindow

现在我们要实现,点击底部的布局弹出我们的文件夹选择框,并且我们弹出框后面的Activity要变暗;

不急着贴代码,我们先考虑下PopupWindow怎么用最好,我们的PopupWindow需要设置布局文件,需要初始化View,需要初始化事件,还需要和Activity交互~~

那么肯定的,我们使用独立的类,这个类和Activity很相似,在里面initView(),initEvent()之类的。

我们创建了一个popupWindow使用的超类:

[java] view plain copy print ?
  1. package com.zhy.utils;
  2. import java.util.List;
  3. import android.content.Context;
  4. import android.graphics.drawable.BitmapDrawable;
  5. import android.view.MotionEvent;
  6. import android.view.View;
  7. import android.view.View.OnTouchListener;
  8. import android.widget.PopupWindow;
  9. public abstract class BasePopupWindowForListView<T> extends PopupWindow
  10. {
  11. /**
  12. * 布局文件的最外层View
  13. */
  14. protected View mContentView;
  15. protected Context context;
  16. /**
  17. * ListView的数据集
  18. */
  19. protected List<T> mDatas;
  20. public BasePopupWindowForListView(View contentView, int width, int height,
  21. boolean focusable)
  22. {
  23. this(contentView, width, height, focusable, null);
  24. }
  25. public BasePopupWindowForListView(View contentView, int width, int height,
  26. boolean focusable, List<T> mDatas)
  27. {
  28. this(contentView, width, height, focusable, mDatas, new Object[0]);
  29. }
  30. public BasePopupWindowForListView(View contentView, int width, int height,
  31. boolean focusable, List<T> mDatas, Object… params)
  32. {
  33. super(contentView, width, height, focusable);
  34. this.mContentView = contentView;
  35. context = contentView.getContext();
  36. if (mDatas != null)
  37. this.mDatas = mDatas;
  38. if (params != null && params.length > 0)
  39. {
  40. beforeInitWeNeedSomeParams(params);
  41. }
  42. setBackgroundDrawable(new BitmapDrawable());
  43. setTouchable(true);
  44. setOutsideTouchable(true);
  45. setTouchInterceptor(new OnTouchListener()
  46. {
  47. @Override
  48. public boolean onTouch(View v, MotionEvent event)
  49. {
  50. if (event.getAction() == MotionEvent.ACTION_OUTSIDE)
  51. {
  52. dismiss();
  53. return true;
  54. }
  55. return false;
  56. }
  57. });
  58. initViews();
  59. initEvents();
  60. init();
  61. }
  62. protected abstract void beforeInitWeNeedSomeParams(Object… params);
  63. public abstract void initViews();
  64. public abstract void initEvents();
  65. public abstract void init();
  66. public View findViewById(int id)
  67. {
  68. return mContentView.findViewById(id);
  69. }
  70. protected static int dpToPx(Context context, int dp)
  71. {
  72. return (int) (context.getResources().getDisplayMetrics().density * dp + 0.5f);
  73. }
  74. }
package com.zhy.utils;import java.util.List;import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.PopupWindow;public abstract class BasePopupWindowForListView<T> extends PopupWindow
{/*** 布局文件的最外层View*/protected View mContentView;protected Context context;/*** ListView的数据集*/protected List<T> mDatas;public BasePopupWindowForListView(View contentView, int width, int height,boolean focusable){this(contentView, width, height, focusable, null);}public BasePopupWindowForListView(View contentView, int width, int height,boolean focusable, List<T> mDatas){this(contentView, width, height, focusable, mDatas, new Object[0]);}public BasePopupWindowForListView(View contentView, int width, int height,boolean focusable, List<T> mDatas, Object... params){super(contentView, width, height, focusable);this.mContentView = contentView;context = contentView.getContext();if (mDatas != null)this.mDatas = mDatas;if (params != null && params.length > 0){beforeInitWeNeedSomeParams(params);}setBackgroundDrawable(new BitmapDrawable());setTouchable(true);setOutsideTouchable(true);setTouchInterceptor(new OnTouchListener(){@Overridepublic boolean onTouch(View v, MotionEvent event){if (event.getAction() == MotionEvent.ACTION_OUTSIDE){dismiss();return true;}return false;}});initViews();initEvents();init();}protected abstract void beforeInitWeNeedSomeParams(Object... params);public abstract void initViews();public abstract void initEvents();public abstract void init();public View findViewById(int id){return mContentView.findViewById(id);}protected static int dpToPx(Context context, int dp){return (int) (context.getResources().getDisplayMetrics().density * dp + 0.5f);}}

也就是封装了一下popupWindow常用的一些设置,然后使用了类似模版方法模式,约束子类,必须实现initView,initEvent,init等方法

[java] view plain copy print ?
  1. package com.zhy.imageloader;
  2. import java.util.List;
  3. import android.view.View;
  4. import android.widget.AdapterView;
  5. import android.widget.AdapterView.OnItemClickListener;
  6. import android.widget.ListView;
  7. import com.zhy.bean.ImageFloder;
  8. import com.zhy.utils.BasePopupWindowForListView;
  9. import com.zhy.utils.CommonAdapter;
  10. import com.zhy.utils.ViewHolder;
  11. public class ListImageDirPopupWindow extends BasePopupWindowForListView<ImageFloder>
  12. {
  13. private ListView mListDir;
  14. public ListImageDirPopupWindow(int width, int height,
  15. List<ImageFloder> datas, View convertView)
  16. {
  17. super(convertView, width, height, true, datas);
  18. }
  19. @Override
  20. public void initViews()
  21. {
  22. mListDir = (ListView) findViewById(R.id.id_list_dir);
  23. mListDir.setAdapter(new CommonAdapter<ImageFloder>(context, mDatas,
  24. R.layout.list_dir_item)
  25. {
  26. @Override
  27. public void convert(ViewHolder helper, ImageFloder item)
  28. {
  29. helper.setText(R.id.id_dir_item_name, item.getName());
  30. helper.setImageByUrl(R.id.id_dir_item_image,
  31. item.getFirstImagePath());
  32. helper.setText(R.id.id_dir_item_count, item.getCount() + ”张”);
  33. }
  34. });
  35. }
  36. public interface OnImageDirSelected
  37. {
  38. void selected(ImageFloder floder);
  39. }
  40. private OnImageDirSelected mImageDirSelected;
  41. public void setOnImageDirSelected(OnImageDirSelected mImageDirSelected)
  42. {
  43. this.mImageDirSelected = mImageDirSelected;
  44. }
  45. @Override
  46. public void initEvents()
  47. {
  48. mListDir.setOnItemClickListener(new OnItemClickListener()
  49. {
  50. @Override
  51. public void onItemClick(AdapterView<?> parent, View view,
  52. int position, long id)
  53. {
  54. if (mImageDirSelected != null)
  55. {
  56. mImageDirSelected.selected(mDatas.get(position));
  57. }
  58. }
  59. });
  60. }
  61. @Override
  62. public void init()
  63. {
  64. // TODO Auto-generated method stub
  65. }
  66. @Override
  67. protected void beforeInitWeNeedSomeParams(Object… params)
  68. {
  69. // TODO Auto-generated method stub
  70. }
  71. }
package com.zhy.imageloader;import java.util.List;import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;import com.zhy.bean.ImageFloder;
import com.zhy.utils.BasePopupWindowForListView;
import com.zhy.utils.CommonAdapter;
import com.zhy.utils.ViewHolder;public class ListImageDirPopupWindow extends BasePopupWindowForListView<ImageFloder>
{private ListView mListDir;public ListImageDirPopupWindow(int width, int height,List<ImageFloder> datas, View convertView){super(convertView, width, height, true, datas);}@Overridepublic void initViews(){mListDir = (ListView) findViewById(R.id.id_list_dir);mListDir.setAdapter(new CommonAdapter<ImageFloder>(context, mDatas,R.layout.list_dir_item){@Overridepublic void convert(ViewHolder helper, ImageFloder item){helper.setText(R.id.id_dir_item_name, item.getName());helper.setImageByUrl(R.id.id_dir_item_image,item.getFirstImagePath());helper.setText(R.id.id_dir_item_count, item.getCount() + "张");}});}public interface OnImageDirSelected{void selected(ImageFloder floder);}private OnImageDirSelected mImageDirSelected;public void setOnImageDirSelected(OnImageDirSelected mImageDirSelected){this.mImageDirSelected = mImageDirSelected;}@Overridepublic void initEvents(){mListDir.setOnItemClickListener(new OnItemClickListener(){@Overridepublic void onItemClick(AdapterView<?> parent, View view,int position, long id){if (mImageDirSelected != null){mImageDirSelected.selected(mDatas.get(position));}}});}@Overridepublic void init(){// TODO Auto-generated method stub}@Overrideprotected void beforeInitWeNeedSomeParams(Object... params){// TODO Auto-generated method stub}}

好了,现在就是我们正在的popupWindow咯,布局文件夹主要是个ListView,所以在initView里面,我们得设置它的适配器;当然了,这里的适配器依然用我们的CommonAdapter,几行代码搞定~~

然后我们需要和Activity交互,当我们点击某个文件夹的时候,外层的Activity需要改变它GridView的数据源,展示我们点击文件夹的图片;

关于交互,我们从Activity的角度去看弹出框,Activity想知道什么,只想知道选择了别的文件夹来告诉我,所以我们创建一个接口OnImageDirSelected,对Activity设置回调;

这里还可以这么写:就是把popupWindow的ListView公布出去,然后在Activity里面使用popupWindow.getListView(),setOnItemClickListener,这么做,个人觉得不好,耦合度太高,客户简单改下需求“这个文件夹展示,给我们换了,换成GridView”,呵呵,此时,你需要到处去修改Activity里面的代码,因为你Activity里面竟然还有个popupWindow.getListView。

好了,扯多了,初始化事件的代码:

[java] view plain copy print ?
  1. @Override
  2. public void initEvents()
  3. {
  4. mListDir.setOnItemClickListener(new OnItemClickListener()
  5. {
  6. @Override
  7. public void onItemClick(AdapterView<?> parent, View view,
  8. int position, long id)
  9. {
  10. if (mImageDirSelected != null)
  11. {
  12. mImageDirSelected.selected(mDatas.get(position));
  13. }
  14. }
  15. });
  16. }
@Overridepublic void initEvents(){mListDir.setOnItemClickListener(new OnItemClickListener(){@Overridepublic void onItemClick(AdapterView<?> parent, View view,int position, long id){if (mImageDirSelected != null){mImageDirSelected.selected(mDatas.get(position));}}});}

如果有人设置了回调,我们就调用;

到此,整个popupWindow就出炉了,接下来就看啥时候让它展示了;

4、选择不同的文件夹

上面说道,当扫描图片完成,拿到包含图片的文件夹信息列表;这个列表就是我们popupWindow所需的数据,所以我们的popupWindow的初始化在handleMessage(上面贴了handler的代码)里面:

在handleMessage里面调用initListDirPopupWindw

[java] view plain copy print ?
  1. /**
  2. * 初始化展示文件夹的popupWindw
  3. */
  4. private void initListDirPopupWindw()
  5. {
  6. mListImageDirPopupWindow = new ListImageDirPopupWindow(
  7. LayoutParams.MATCH_PARENT, (int) (mScreenHeight * 0.7),
  8. mImageFloders, LayoutInflater.from(getApplicationContext())
  9. .inflate(R.layout.list_dir, null));
  10. mListImageDirPopupWindow.setOnDismissListener(new OnDismissListener()
  11. {
  12. @Override
  13. public void onDismiss()
  14. {
  15. // 设置背景颜色变暗
  16. WindowManager.LayoutParams lp = getWindow().getAttributes();
  17. lp.alpha = 1.0f;
  18. getWindow().setAttributes(lp);
  19. }
  20. });
  21. // 设置选择文件夹的回调
  22. mListImageDirPopupWindow.setOnImageDirSelected(this);
  23. }
/*** 初始化展示文件夹的popupWindw*/private void initListDirPopupWindw(){mListImageDirPopupWindow = new ListImageDirPopupWindow(LayoutParams.MATCH_PARENT, (int) (mScreenHeight * 0.7),mImageFloders, LayoutInflater.from(getApplicationContext()).inflate(R.layout.list_dir, null));mListImageDirPopupWindow.setOnDismissListener(new OnDismissListener(){@Overridepublic void onDismiss(){// 设置背景颜色变暗WindowManager.LayoutParams lp = getWindow().getAttributes();lp.alpha = 1.0f;getWindow().setAttributes(lp);}});// 设置选择文件夹的回调mListImageDirPopupWindow.setOnImageDirSelected(this);}

我们初始化我们的popupWindow,设置了关闭对话框的回调,已经设置了选择不同文件夹的回调;
这里仅仅是初始化,下面看我们合适将其弹出的,其实整个Activity也就一个事件,点击弹出该对话框,所以看Activity的initEvents方法:

[java] view plain copy print ?
  1. private void initEvent()
  2. {
  3. /**
  4. * 为底部的布局设置点击事件,弹出popupWindow
  5. */
  6. mBottomLy.setOnClickListener(new OnClickListener()
  7. {
  8. @Override
  9. public void onClick(View v)
  10. {
  11. mListImageDirPopupWindow
  12. .setAnimationStyle(R.style.anim_popup_dir);
  13. mListImageDirPopupWindow.showAsDropDown(mBottomLy, 0, 0);
  14. // 设置背景颜色变暗
  15. WindowManager.LayoutParams lp = getWindow().getAttributes();
  16. lp.alpha = .3f;
  17. getWindow().setAttributes(lp);
  18. }
  19. });
  20. }
private void initEvent(){/*** 为底部的布局设置点击事件,弹出popupWindow*/mBottomLy.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v){mListImageDirPopupWindow.setAnimationStyle(R.style.anim_popup_dir);mListImageDirPopupWindow.showAsDropDown(mBottomLy, 0, 0);// 设置背景颜色变暗WindowManager.LayoutParams lp = getWindow().getAttributes();lp.alpha = .3f;getWindow().setAttributes(lp);}});}

可以看到,我们为底部布局设置点击事件;设置popupWindow的弹出与消失的动画;已经让Activity背景变暗变亮,通过改变Window alpha实现的。变亮在弹出框消息的监听里面~~

动画的文件就不贴了,大家自己看源码;

popupWindow弹出了,用户此时可以选择不同的文件夹,那么现在该看选择后的回调的代码了:

我们的Activity实现了该接口,直接看实现的方法:

[java] view plain copy print ?
  1. @Override
  2. public void selected(ImageFloder floder)
  3. {
  4. mImgDir = new File(floder.getDir());
  5. mImgs = Arrays.asList(mImgDir.list(new FilenameFilter()
  6. {
  7. @Override
  8. public boolean accept(File dir, String filename)
  9. {
  10. if (filename.endsWith(“.jpg”) || filename.endsWith(“.png”)
  11. || filename.endsWith(”.jpeg”))
  12. return true;
  13. return false;
  14. }
  15. }));
  16. /**
  17. * 可以看到文件夹的路径和图片的路径分开保存,极大的减少了内存的消耗;
  18. */
  19. mAdapter = new MyAdapter(getApplicationContext(), mImgs,
  20. R.layout.grid_item, mImgDir.getAbsolutePath());
  21. mGirdView.setAdapter(mAdapter);
  22. // mAdapter.notifyDataSetChanged();
  23. mImageCount.setText(floder.getCount() + ”张”);
  24. mChooseDir.setText(floder.getName());
  25. mListImageDirPopupWindow.dismiss();
  26. }
   @Overridepublic void selected(ImageFloder floder){mImgDir = new File(floder.getDir());mImgs = Arrays.asList(mImgDir.list(new FilenameFilter(){@Overridepublic boolean accept(File dir, String filename){if (filename.endsWith(".jpg") || filename.endsWith(".png")|| filename.endsWith(".jpeg"))return true;return false;}}));/*** 可以看到文件夹的路径和图片的路径分开保存,极大的减少了内存的消耗;*/mAdapter = new MyAdapter(getApplicationContext(), mImgs,R.layout.grid_item, mImgDir.getAbsolutePath());mGirdView.setAdapter(mAdapter);// mAdapter.notifyDataSetChanged();mImageCount.setText(floder.getCount() + "张");mChooseDir.setText(floder.getName());mListImageDirPopupWindow.dismiss();}

我们改变了GridView的适配器,以及底部的控件上的文件夹名称,文件数量等等;

好了,到此结束;整篇由于篇幅原因没有贴任何布局文件,大家自己通过源码查看;

在此希望大家可以通过该案例,能够去其糟粕,取其精华,学习其中值得借鉴的代码风格,不要真的当作一个例子去学习~~

源码点击下载

ps:请真机测试,反正我的模拟器扫描不到图片~

ps:运行出现空指针的话,在getImages中添加判断,if(parentFile.list()==null)continue , 切记~~~具体位置,上面有说;

———————————————————————————————————

我建了一个QQ群,方便大家交流。群号:55032675

———————————————————————————————————-

博主部分视频已经上线,如果你不喜欢枯燥的文本,请猛戳(初录,期待您的支持):

1、高仿微信5.2.1主界面及消息提醒

2、高仿QQ5.0侧滑

Android 高仿微信图片选择器相关推荐

  1. android qq底部图片选择器,Android 高仿QQ图片选择器

    当做一款APP,需要选择本地图片时,首先考虑的无疑是系统相册,但是Android手机五花八门,再者手机像素的提升,大图无法返回等异常因数,导致适配机型比较困难,微信.QQ都相继的在自己的APP里集成了 ...

  2. Android 超高仿微信图片选择器 图片该这么加载

    2019独角兽企业重金招聘Python工程师标准>>> 转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39943 ...

  3. Android 超高仿微信图片选择器 图片该这么加载

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/39943731,本文出自: [张鸿洋的博客] 1.概述 关于手机图片加载器,在当 ...

  4. Android 超高仿微信图片选择器 图片该这么载入

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/39943731,本文出自: [张鸿洋的博客] 1.概述 关于手机图片载入器,在当 ...

  5. Android高仿微信照片选择器+预览+显示照片

    转载请说明出处: http://blog.csdn.net/lyhhj/article/details/49046109 前阵子写过一片博客,是关于选择多图上传的一个小demo,那个demo是从网上找 ...

  6. Android高仿微信图片多选功能

    1.概述 关于手机图片加载器,在当今像素随随便便破千万的时代,一张图片占据的内存都相当可观,作为高大尚程序猿的我们,有必要掌握图片的压缩,缓存等处理,以到达纵使你有万张照片,纵使你的像素再高,我们也能 ...

  7. Android 超高仿微信图片选择器

    出处: http://blog.csdn.net/lmj623565791/article/details/39943731 1.概述 关于手机图片加载器,在当今像素随随便便破千万的时代,一张图片占据 ...

  8. android 微信图片选择,Android之仿微信图片选择器

    先上效果图.第一张图显示的是"相机"文件夹中的所有图片:通过点击多张图片可以到第二张图所示的效果(被选择的图片会变暗,同时选择按钮变亮):点击最下面的那一栏可以到第三张图所示的效果 ...

  9. android仿微信图片上传进度,android高仿微信发布动态(选择图片)

    [实例简介]Android 超高仿微信图片选择器 [实例截图] [核心代码] public class MainActivity extends Activity implements OnImage ...

最新文章

  1. 第二章 循环结构程序设计
  2. PHP获取二维数组中某一列的值集合
  3. 深度解析:会用Excel,还有必要学Python吗?
  4. php 价钱计算,php公式计算
  5. 课题开题报告范文样本_成都汽车职业技术学校举行 2020年省、市、区课题开题报告会...
  6. CVPR 2019 论文大盘点-人脸技术篇
  7. 期权协议Charm在主网上线为UniswapV3设计的被动做市策略AlphaVaults
  8. 深度搜索 java_java实现的深度搜索与广度搜索算法BFS,DFS以及几种最短路径算法...
  9. C++ 开发者怒了:这个无用的模块设计最终会害死 C++!
  10. bzoj1010: [HNOI2008]玩具装箱toy
  11. 怎样使用计算机解方程,【教程】用计算器解方程(牛顿法)
  12. 智能客服、聊天机器人的应用和架构、算法分享和介绍
  13. MVP简单使用+RecyclerView
  14. docker deamon源码学习
  15. linux firefox 显示PDF,某个 pdf 文件用 firefox 和 okular 显示乱码
  16. 大数据是什么,大数据的特点主要有哪些,应该怎么运用?
  17. oracle asm无法关闭,ASM无法关闭 - Oracle专题深入讨论 - ITPUB论坛-中国专业的IT技术社区...
  18. 计算机新技术在体育中的应用,计算机虚拟现实技术在体育训练的应用
  19. linux电脑主机国产,免费linux主机面板推荐,国产比较优秀Linux免费云主机管理面...
  20. python3跑通smpl模型_SMPL模型学习

热门文章

  1. 碎片化时代,别让用户费脑子
  2. 白盒测试的条件覆盖标准强于判定覆盖为什么不对?什么是逻辑覆盖?覆盖强弱关系?判定覆盖与条件覆盖有什么不同?【知足且坚定,温柔且上进---两牛博客】
  3. 软件测试中条件覆盖例子,判定覆盖条件覆盖软件测试.ppt
  4. 下周要去女朋友家,第一次去应该给未来老丈人带什么礼物合适,要轻奢、时尚有独特性的,求推荐?
  5. 七夕送女朋友什么礼物?七夕好用、实用好物推荐
  6. java 中 Scanner 输入
  7. 视频教程-企业如何防范WebShell 攻击-云安全
  8. OS X Mavericks资源整合
  9. pgstgresql 分区表
  10. fft频谱 matlab,matlab流水线ADC仿真FFT频谱测试【求高手搭救】