前言
在很多很多的项目中,都有选择本地图片的功能,现在就带大家做一个仿微信的图片选择器
1.和微信相比,由于博主是平板,微信在博主的平板中的图片是很模糊的,而我们的这个比微信的清晰,但是代价基本就是内存的多消耗,但是现在的收集基本上这点内存还是有的,图片也是经过压缩的
2.和鸿洋封装的相比,有些人可能会说和大神的有可比性么?我可以很直白的说这个图片选择器就是参考鸿洋大神以前封装的图片选择器,并且进行代码的分层、逻辑的重新梳理、优化显示效果、去除很多难懂的代码,用浅显易懂的代码实现之!,并且图片的显示进行了适配,无论在大的平板还是小的手机上显示都不会觉得很过分.用RecyclerView取代了鸿洋大神使用的ListView,为以后改效果做下了很好的铺垫
但是鸿洋大神的选择器给了我一些很好的思路,在这里表示感谢!
先放上效果图:
纵向的滑动
上面的一些地方博主就不美化了,啥图片太大呀,文字太大之类的就留给亲们自行去更改吧,美化对博主来说是一件难得事情~~~~嗯
使用的是RecyclerView,所以你可以轻松的改成其他的样式,比如比较炫酷的瀑布流之类的,这就靠你自己去扩展了~~~
好了,下面开始正片~~~咳咳咳,是正文
梳理制作流程
1.提出问题
a)我们显示的图片在哪里?
b)他们在哪些文件夹中?
c)图片如何加载(其实就是如何加载缩略图,说白点就是如何压缩图)
2.针对问题,制定方案
问题a,b其实是一个问题,这么说呢?我们可以为我们的app和本地图片之间构建一个管理者LocalImageManager,当你想获取图片的路径的时候找他!当你想知道图片都在哪里文件夹中,找他!当你想获取某一个文件夹中的图片的时候,也找他.归根结底这个类就是要给对本地图片的一个管理者
问题c,我们需要定制一个自己的本地图片加载器,只要提供图片控件ImageView 和 图片的路径ImagePath,图片加载器就可以加载缩略图到控件中显示
写代码!
编写本地的图片管理者LocalImageManager和本地图片信息持有者LocalImageInfo
/** * Created by cxj on 2016/5/4. * 本地图片的一个信息,对图片的一个描述 */ public class LocalImageInfo { /** * 信息的图片类型的数组 * 图片的类型 * {@link LocalImageManager#JPEG_MIME_TYPE} * {@link LocalImageManager#PNG_MIME_TYPE} * {@link LocalImageManager#JPG_MIME_TYPE} */ private String[] mimeType; /** * 一个对象的图片类型是定死的,方便维护,对象一经创建 * 这个类型就更改不了了 * * @param mimeType 图片的类型 * {@link LocalImageManager#JPEG_MIME_TYPE} * {@link LocalImageManager#PNG_MIME_TYPE} * {@link LocalImageManager#JPG_MIME_TYPE} */ public LocalImageInfo(String[] mimeType) { //如果传进来是空的,直接就挂了 this.mimeType = new String[mimeType.length]; for (int i = 0; i < mimeType.length; i++) { this.mimeType[i] = mimeType[i]; } } /** * 图片文件的文件夹路径 */ private Set<String> imageFolders = new HashSet<String>(); /** * 所有图片文件的路径 */ private List<String> imageFiles = new ArrayList<String>(); /** * 一个map集合,用于保存对应文件夹路径对应的图片路径的集合 */ private Map<String, List<String>> map = new HashMap<String, List<String>>(); /** * 获取一个新的类型数组,和原来的不一样 * * @return */ public String[] getMimeType() { String[] arr = new String[this.mimeType.length]; for (int i = 0; i < arr.length; i++) { arr[i] = this.mimeType[i]; } return arr; } public Set<String> getImageFolders() { return imageFolders; } public void setImageFolders(Set<String> imageFolders) { this.imageFolders = imageFolders; } public List<String> getImageFiles() { return imageFiles; } public void setImageFiles(List<String> imageFiles) { this.imageFiles = imageFiles; } public List<String> getImagesByFolderPath(String folderPath) { return map.get(folderPath); } public void setImagesByFolderPath(String folderPath, List<String> list) { map.put(folderPath, list); } }里面有维护mime_type类型的操作,这是防止有的人获取mime_type类型然后更改掉,这里的原则是一个LocalImageInfo对应一个信息,那图片的类型当然是创建了就不能更改啦!所以你可以看到getMimeType的时候我都是创建一个信息返回的
/** * Created by cxj on 2016/5/4. * a manager of localImage * require permission:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> * you can copy to your AndroidManifest.xml */ public class LocalImageManager { /** * png图片格式的类型 * the mime_type of png */ public static final String PNG_MIME_TYPE = "image/png"; /** * png图片的后缀 */ public static final String PNG_SUFFIX = "png"; /** * jpeg图片格式的类型 * the mime_type of jpeg */ public static final String JPEG_MIME_TYPE = "image/jpeg"; /** * jpg图片格式的类型 * the mime_type of jpg */ public static final String JPG_MIME_TYPE = "image/jpeg"; /** * jpeg图片的后缀 */ public static final String JPEG_SUFFIX = "jpeg"; /** * jpg图片的后缀 */ public static final String JPG_SUFFIX = "jpg"; /** * 上下文对象 * the environment of app */ private static Context context; /** * init * * @param context */ public static void init(Context context) { if (LocalImageManager.context == null) { LocalImageManager.context = context; } } /** * 根据文件夹的路径,查询对应mime_type类型的图片路径的集合 * * @param localImageInfo 可以为NULL,当为NULL的时候,不缓存 * @param folderPath * @return */ @Nullable public static List<String> queryImageByFolderPath(LocalImageInfo localImageInfo, String folderPath) { //图片的类型 String[] mimeType = localImageInfo.getMimeType(); //健壮性判断 if (folderPath == null || "".equals(folderPath) || mimeType.length == 0) { return null; } //声明返回值 List<String> images; //如果只是单纯的查询,一次性的,那么可以传入localImageInfo为空 if (localImageInfo == null) { images = new ArrayList<String>(); } else { //否则从缓存中拿出来看看 images = localImageInfo.getImagesByFolderPath(folderPath); } //如果没有,那就自己去查询 if (images == null) { //创建集合 images = new ArrayList<String>(); //创建文件对象爱那个 File folder = new File(folderPath); //如果文件夹存在 if (folder.exists() && folder.isDirectory()) { //获取所有的文件对象 File[] files = folder.listFiles(); //循环所有的文件对象 for (int i = 0; i < files.length; i++) { File file = files[i]; //如果是文件,而不是文件夹,并且文件的后缀匹配了 if (file.isFile() && isFileMatchMimeType(file, mimeType)) { //添加到集合中 images.add(file.getPath()); } } if (localImageInfo != null) { //集合保存起来,下次调用的时候就不会查询了,直接返回 localImageInfo.setImagesByFolderPath(folderPath, images); } return images; } } else { return images; } return null; } /** * 查询系统中存储的所有图片的路径和文件夹的路径 * 其实就是 * {@link LocalImageManager#queryImage(LocalImageInfo)} * 和 * {@link LocalImageManager#queryAllFolders(LocalImageInfo)} * 的合体 * * @param localImageInfo 本地图片的描述对象 * @return */ @Nullable public static LocalImageInfo queryImageWithFolder(LocalImageInfo localImageInfo) { String[] mimeType = localImageInfo.getMimeType(); if (mimeType.length == 0) { return localImageInfo; } return queryAllFolders(queryImage(localImageInfo)); } /** * 查询出所有图片的文件夹的路径,根据{@link LocalImageInfo#imageFiles}集合进行整理的 * 结果放在{@link LocalImageInfo#imageFolders} * * @param localImageInfo 本地图片的一个描述信息 * @return */ private static LocalImageInfo queryAllFolders(LocalImageInfo localImageInfo) { //获取图片的文件夹 List<String> imageFiles = localImageInfo.getImageFiles(); //获取存放图片路径的文件夹集合 Set<String> set = localImageInfo.getImageFolders(); set.clear(); int size = imageFiles.size(); //循环所有的图片路径,找出所有的文件夹路径,不能重复 for (int i = 0; i < size; i++) { File imageFile = new File(imageFiles.get(i)); if (imageFile.exists()) { File parentFile = imageFile.getParentFile(); if (parentFile.exists()) { set.add(parentFile.getPath()); } } } return localImageInfo; } /** * query by mime_type of image * 根据图片的类型进行查询,查询的是系统中存储的图片信息 * 结果放在{@link LocalImageInfo#imageFiles} * * @param localImageInfo 本地图片的一个描述信息 * @return */ public static LocalImageInfo queryImage(LocalImageInfo localImageInfo) { String[] mimeType = localImageInfo.getMimeType(); if (mimeType.length == 0) { return localImageInfo; } Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; ContentResolver mContentResolver = context .getContentResolver(); //查询的条件 StringBuffer selection = new StringBuffer(); //利用循环生成条件 for (int i = 0; i < mimeType.length; i++) { //图片的类型 String mime = mimeType[i]; if (i == mimeType.length - 1) { //如果是最后一个 selection.append(MediaStore.Images.Media.MIME_TYPE + " = ?"); } else { selection.append(MediaStore.Images.Media.MIME_TYPE + " = ? or "); } } //执行查询 Cursor mCursor = mContentResolver.query(mImageUri, null, selection.toString(), mimeType, MediaStore.Images.Media.DATE_MODIFIED); List<String> imageFiles = localImageInfo.getImageFiles(); imageFiles.clear(); //循环结果集 while (mCursor.moveToNext()) { // 获取图片的路径 String imagePath = mCursor.getString(mCursor .getColumnIndex(MediaStore.Images.Media.DATA)); imageFiles.add(imagePath); } return localImageInfo; } /** * 文件的后缀是不是匹配mimeType类型 * * @param file 不能为空 must not be NULL * @param mime_type 不能为空 must not be NULL * @return */ private static boolean isFileMatchMimeType(File file, String... mime_type) { String SUFFIX = StringUtil.getLastContent(file.getName(), ".").toLowerCase(); for (int i = 0; i < mime_type.length; i++) { switch (mime_type[i]) { case PNG_MIME_TYPE: if (PNG_SUFFIX.equals(SUFFIX)) { return true; } break; case JPEG_MIME_TYPE: if (JPEG_SUFFIX.equals(SUFFIX) || JPG_SUFFIX.equals(SUFFIX)) { return true; } break; } } return false; } }注释写的很清楚啦,这里不解释每一个方法的具体作用了,我们可以看到我们的每一个方法都是接受一个LocalImageInfo参数的,这和我们刚刚设计的是一致的,设计的时候怎么样,代码也应该是怎么样的
LocalImageManager中几个重要的方法,这里罗列出来:
a) queryImage:用于查询Android系统中保存的图片信息
b) queryAllFolders 根据查询出来的图片信息,整理出有多少的文件夹
c) queryImageWithFolder a和b方法的结合体
d) queryImageByFolderPath 根据指定的路径查询图片信息,如果传入图片信息持有者LocalImageInfo,就会把查询的结果缓存到对象中,反之不缓存
编写主界面代码
主界面xml文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.cxj.imageselect.ui.SelectLocalImageAct"> <RelativeLayout android:id="@+id/rl_act_main_titlebar" android:paddingTop="8dp" android:paddingBottom="8dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:layout_width="match_parent" android:background="#000066" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_act_main_back" android:layout_width="wrap_content" android:layout_centerVertical="true" android:src="@mipmap/arrow_left" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_act_main_ok" android:layout_width="wrap_content" android:layout_centerVertical="true" android:layout_height="wrap_content" android:padding="2dp" android:layout_alignParentRight="true" android:textSize="22sp" android:background="#006800" android:textColor="#FFFFFF" android:text="我选好了"/> </RelativeLayout> <android.support.v7.widget.RecyclerView android:id="@+id/rv" android:layout_below="@+id/rl_act_main_titlebar" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.RecyclerView> <LinearLayout android:id="@+id/ll_act_main_info" android:layout_alignParentBottom="true" android:layout_width="match_parent" android:orientation="horizontal" android:background="#EE4d4d4d" android:padding="5dp" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_act_main_image_folder_name" android:layout_width="0dp" android:layout_weight="1" android:text="image_folder" android:textSize="22sp" android:textColor="#FFFFFF" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_act_main_image_number" android:layout_width="wrap_content" android:text="image_number" android:textColor="#FFFFFF" android:textSize="22sp" android:layout_height="wrap_content" /> </LinearLayout> </RelativeLayout>
Activity代码实现
/** * 选择本地图片的activity */ public class SelectLocalImageAct extends Activity implements View.OnClickListener, CommonRecyclerViewAdapter.OnRecyclerViewItemClickListener, Runnable { /** * 类的标识 */ public static final String TAG = "SelectLocalImageAct"; /** * activity带回去的数据的标识 */ public static final String RETURN_DATA_FLAG = "data"; /** * 结果码 */ public static final int RESULT_CODE = 666; private RecyclerView rv = null; /** * PopupWindow */ private ListImageDirPopupWindow listImageDirPopupWindow; /** * 返回图标 */ private ImageView iv_back; /** * 确定 */ private TextView tv_ok; /** * 底部的控件 */ private LinearLayout ll_info = null; /** * 显示文件夹名称的控件 */ private TextView tv_folderName; /** * 显示文件夹中图片文件的个数 */ private TextView tv_imageNumber; /** * 适配器 */ private CommonRecyclerViewAdapter<String> adapter; /** * 本地图片的信息 */ private LocalImageInfo localImageInfo = new LocalImageInfo(new String[]{LocalImageManager.PNG_MIME_TYPE, LocalImageManager.JPEG_MIME_TYPE, LocalImageManager.JPG_MIME_TYPE}); /** * 显示的数据u */ private List<String> data = new ArrayList<String>(); /** * 记录图片是不是被选中,利用下标进行关联 */ private List<Boolean> imageStates = new ArrayList<Boolean>(); /** * 上下文 */ private Context context; private Handler h = new Handler() { @Override public void handleMessage(Message msg) { MessageDataHolder m = (MessageDataHolder) msg.obj; //设置底部的信息 tv_folderName.setText(m.folderName.length() > 12 ? m.folderName.substring(0, 12) + "..." : m.folderName); tv_imageNumber.setText(m.imageNum + "张"); //初始化选中状态的记录集合 imageStates.clear(); for (int i = 0; i < localImageInfo.getImageFiles().size(); i++) { imageStates.add(false); } data.clear(); data.addAll(localImageInfo.getImageFiles()); //关闭弹出窗口 listImageDirPopupWindow.dismiss(); setBackAlpha(false); //通知数据改变 adapter.notifyDataSetChanged(); listImageDirPopupWindow.notifyDataSetChanged(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); EBus.register(TAG, this); //初始化控件 initView(); //初始化事件 initEvent(); //线程池执行任务 ThreadPool.getInstance().invoke(this); } @Override protected void onDestroy() { super.onDestroy(); EBus.unRegister(TAG); } /** * 初始化监听事件 */ private void initEvent() { iv_back.setOnClickListener(this); tv_ok.setOnClickListener(this); //设置底部菜单的监听 ll_info.setOnClickListener(this); //设置ReCyclerView的条目监听 adapter.setOnRecyclerViewItemClickListener(this); } /** * 初始化控件 */ private void initView() { context = this; //寻找一些控件 iv_back = (ImageView) findViewById(R.id.iv_act_main_back); tv_ok = (TextView) findViewById(R.id.tv_act_main_ok); ll_info = (LinearLayout) findViewById(R.id.ll_act_main_info); tv_folderName = (TextView) findViewById(R.id.tv_act_main_image_folder_name); tv_imageNumber = (TextView) findViewById(R.id.tv_act_main_image_number); rv = (RecyclerView) findViewById(R.id.rv); //创建适配器 adapter = new ImageAdapter(context, data, imageStates); //设置适配器 rv.setAdapter(adapter); rv.setLayoutManager(new GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)); //初始化弹出窗口 initPopuWindow(); } @Override public void onClick(View v) { int id = v.getId(); switch (id) { case R.id.ll_act_main_info: if (listImageDirPopupWindow != null) { listImageDirPopupWindow .setAnimationStyle(R.style.anim_popup_dir); listImageDirPopupWindow.showAsDropDown(ll_info, 0, 0); } // 设置背景颜色变暗 setBackAlpha(true); break; case R.id.iv_act_main_back: finish(); break; case R.id.tv_act_main_ok: Intent i = new Intent(); i.putExtra(RETURN_DATA_FLAG, getSelectImages()); setResult(RESULT_CODE, i); finish(); break; } } @Override public void onItemClick(View v, int position) { imageStates.set(position, !imageStates.get(position)); adapter.notifyDataSetChanged(); } /** * 根据文件夹的路径,进行加载图片 * * @param folderPath */ public void onEventLoadImageByFolderPath(final String folderPath) { if ("System".equals(folderPath)) { //线程池执行任务 ThreadPool.getInstance().invoke(this); return; } File folder = new File(folderPath); //文件夹存在并且是一个目录 if (folder.exists() && folder.isDirectory()) { ThreadPool.getInstance().invoke(new Runnable() { @Override public void run() { List<String> tmpData = LocalImageManager.queryImageByFolderPath(localImageInfo, folderPath); localImageInfo.getImageFiles().clear(); localImageInfo.getImageFiles().addAll(tmpData); //发送消息 h.sendMessage(MessageDataHolder.obtain(folderPath, localImageInfo.getImageFiles().size())); } }); } } @Override public void run() { //初始化本地图片的管理者 LocalImageManager.init(context); //获取本地系统图片的信息,并且整理文件夹 LocalImageManager. queryImageWithFolder(localImageInfo); //发送消息 h.sendMessage(MessageDataHolder.obtain("所有文件", localImageInfo.getImageFiles().size())); } /** * 初始化弹出的窗口 */ private void initPopuWindow() { //初始化弹出框 View contentView = View.inflate(context, R.layout.list_dir, null); //创建要弹出的popupWindow listImageDirPopupWindow = new ListImageDirPopupWindow(contentView, ScreenUtils.getScreenWidth(context), ScreenUtils.getScreenHeight(context) * 2 / 3, true, localImageInfo); //消失的时候监听 listImageDirPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() { @Override public void onDismiss() { setBackAlpha(false); } }); } /** * 设置窗体的透明度,根据PopupWindow是否打开 * * @param isOpen */ private void setBackAlpha(boolean isOpen) { WindowManager.LayoutParams lp = getWindow().getAttributes(); if (isOpen) { lp.alpha = .3f; } else { lp.alpha = 1.0f; } getWindow().setAttributes(lp); } /** * 获取被选中的图片的数组 * * @return */ private String[] getSelectImages() { List<String> tmp = new ArrayList<String>(); for (int i = 0; i < imageStates.size(); i++) { if (imageStates.get(i)) { tmp.add(localImageInfo.getImageFiles().get(i)); } } String[] arr = new String[tmp.size()]; for (int i = 0; i < tmp.size(); i++) { arr[i] = tmp.get(i); } tmp = null; return arr; } }
代码比较长,这里慢慢解释,一些简单的和一些初始化的代码就不细讲了,这里就讲一下加载数据的过程
选中效果的实现
编写弹出窗口ListImageDirPopupWindow
弹出窗口的布局xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#ffffff" > <ListView android:id="@+id/lv_list_dir" android:layout_width="fill_parent" android:layout_height="fill_parent" android:divider="#EEE3D9" android:dividerHeight="1px" > </ListView> </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="5dp" > <ImageView android:id="@+id/id_dir_item_image" android:layout_width="100dp" android:layout_height="100dp" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:background="@drawable/pic_dir" android:paddingBottom="17dp" android:paddingLeft="12dp" android:paddingRight="12dp" android:paddingTop="9dp" android:scaleType="fitXY" android:src="@mipmap/folder" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:layout_toRightOf="@id/id_dir_item_image" android:orientation="vertical" > <TextView android:id="@+id/id_dir_item_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="所有图片" android:textSize="16sp" /> <TextView android:id="@+id/id_dir_item_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="4723张" android:textColor="#444" android:textSize="12sp" /> </LinearLayout> </RelativeLayout>
PopupWindow的实现代码
/** * Created by cxj on 2016/5/5. */ public class ListImageDirPopupWindow<T> extends PopupWindow implements View.OnTouchListener { /** * 布局文件的最外层View */ protected View mContentView; /** * 上下文对象 */ protected Context context; /** * 本地图片的信息 */ private LocalImageInfo localImageInfo; /** * ListView */ private ListView lv = null; /** * 适配器 */ private BaseAdapter adapter; /** * 显示的数据 */ private List<String> data; /** * 系统中的图片个数 */ private int systemImageNum = 0; public ListImageDirPopupWindow(View contentView, int width, int height, boolean focusable, LocalImageInfo localImageInfo) { super(contentView, width, height, focusable); this.localImageInfo = localImageInfo; data = ArrayUtil.setToList(localImageInfo.getImageFolders()); data.add(0, "System"); //根布局 this.mContentView = contentView; //上下文 context = contentView.getContext(); //设置popupWindow个几个属性 setBackgroundDrawable(new ColorDrawable(Color.parseColor("#00000000"))); setTouchable(true); setOutsideTouchable(true); //触摸的拦截事件 setTouchInterceptor(this); //初始化控件 initView(); initData(); initEvent(); } /** * 初始化事件 */ private void initEvent() { lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //发送纤细让Activity加载指定的文件夹中的图片 EBus.postEvent(SelectLocalImageAct.TAG, "loadImageByFolderPath", data.get(position)); } }); } /** * 初始化数据 */ private void initData() { //创建适配器 adapter = new CommonAdapter<String>(context, data, R.layout.list_dir_item) { @Override public void convert(CommonViewHolder h, String item, int position) { //设置目录名称 h.setText(R.id.id_dir_item_name, item); if (position == 0) { //第一个条目不是目录,特殊考虑 h.setText(R.id.id_dir_item_count, systemImageNum + "张"); } else { List<String> list = LocalImageManager.queryImageByFolderPath(localImageInfo, item); h.setText(R.id.id_dir_item_count, list.size() + "张"); } } }; //设置适配器 lv.setAdapter(adapter); } /** * 通知数据改变 */ public void notifyDataSetChanged() { List<String> list = ArrayUtil.setToList(localImageInfo.getImageFolders()); if (systemImageNum == 0) { systemImageNum = localImageInfo.getImageFiles().size(); } data.clear(); data.add("System"); data.addAll(list); list = null; adapter.notifyDataSetChanged(); } /** * 查找控件封装 * * @param id 控件的id * @return */ public View findViewById(int id) { return mContentView.findViewById(id); } /** * 初始化控件 */ private void initView() { lv = (ListView) findViewById(R.id.lv_list_dir); } @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { dismiss(); return true; } return false; } }
编写加载本地图片的压轴戏
1.如何压缩图片
/** * 有关图像的工具类 * 功能1.加载本地图片,压缩的形式 * * @author xiaojinzi */ public class ImageUtil { /** * 加载一个本地的图片 * * @param localImagePath * @param reqWidth * @param reqHeight * @return */ public static Bitmap decodeLocalImage(String localImagePath, int reqWidth, int reqHeight) { //获取大图的参数,包括大小 BitmapFactory.Options options = getBitMapOptions(localImagePath); //获取一个合适的缩放比例 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 计算inSampleSize //让BitmapFactory加载真的图片,等于true的时候只加载了图片的大小 options.inJustDecodeBounds = false; Bitmap src = BitmapFactory.decodeFile(localImagePath, options); // 载入一个稍大的缩略图 return createScaleBitmap(src, reqWidth, reqHeight); // 进一步得到目标大小的缩略图 } /** * 计算inSampleSize,用于压缩图片 * * @param options * @param reqWidth * @param reqHeight * @return */ // private static int calculateInSampleSize(BitmapFactory.Options options, // int reqWidth, int reqHeight) { // // 源图片的宽度 // int width = options.outWidth; // int height = options.outHeight; // int inSampleSize = 1; // // if (width > reqWidth && height > reqHeight) { // // 计算出实际宽度和目标宽度的比率 // int widthRatio = Math.round((float) width / (float) reqWidth); // int heightRatio = Math.round((float) width / (float) reqWidth); // inSampleSize = Math.max(widthRatio, heightRatio); // } // return inSampleSize; // } private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } /** * 如果是放大图片,filter决定是否平滑,如果是缩小图片,filter无影响 * * @param src * @param dstWidth * @param dstHeight * @return */ private static Bitmap createScaleBitmap(Bitmap src, int dstWidth, int dstHeight) { if (src == null) { return null; } Bitmap dst = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false); if (src != dst) { // 如果没有缩放,那么不回收 src.recycle(); // 释放Bitmap的native像素数组 } return dst; } /** * 获取本地图片的大小 * require permission: * <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> * * @param localImagePath * @return */ public static BitmapFactory.Options getBitMapOptions(String localImagePath) { final BitmapFactory.Options options = new BitmapFactory.Options(); //表示不是真的加载图片的数据,只是计算了图片的大小 options.inJustDecodeBounds = true; //计算出了图片的大小,没有真正的加载 BitmapFactory.decodeFile(localImagePath, options); return options; } /** * 设置bitmap的宽和高 * * @param b * @param width * @param height * @return */ public static Bitmap setBitmap(Bitmap b, int width, int height) { return Bitmap.createScaledBitmap(b, width < 1 ? 1 : width, height < 1 ? 1 : height, false); } /** * 获取一个自适应的图片资源 * * @param bitmap * @param context * @return */ public static Bitmap getResizedBitmap(Bitmap bitmap, Context context) { int height = ScreenUtils.getScreenHeight(context); int width = ScreenUtils.getScreenWidth(context); if (height < 480 && width < 320) { return Bitmap.createScaledBitmap(bitmap, 32, 32, false); } else if (height < 800 && width < 480) { return Bitmap.createScaledBitmap(bitmap, 48, 48, false); } else if (height < 1024 && width < 600) { return Bitmap.createScaledBitmap(bitmap, 72, 72, false); } else { return Bitmap.createScaledBitmap(bitmap, 96, 96, false); } } }其中的各个方法都是参考网上搜索的代码然后加以修改完成的
//获取本地的图片的压缩图 Bitmap bm = ImageUtil.decodeLocalImage(imageLocalPath, 100, 100);
编写图片加载器
/** * 本地的图片加载器 */ public class LocalImageLoader { private static ThreadPool threadPool; /** * 自身 */ private static LocalImageLoader localImageLoader = null; /** * 图片缓存的核心类 */ private static LruCache<String, Bitmap> mLruCache; /** * 用于设置图片到控件上 */ private Handler h = new Handler() { @Override public void handleMessage(Message msg) { ImgBeanHolder holder = (ImgBeanHolder) msg.obj; ImageView imageView = holder.imageView; Bitmap bm = holder.bitmap; String path = holder.path; if (imageView.getTag().toString().equals(path)) { imageView.setImageBitmap(bm); } else { } } }; /** * 获取一个实例对象 * * @return */ public static synchronized LocalImageLoader getInstance() { if (localImageLoader == null) { localImageLoader = new LocalImageLoader(); threadPool = ThreadPool.getInstance(); // 获取应用程序最大可用内存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory / 8; mLruCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } }; } return localImageLoader; } public void loadImage(final String imageLocalPath, final ImageView imageView) { imageView.setTag(imageLocalPath); //从一级缓存中拿 Bitmap bitmap = mLruCache.get(imageLocalPath); if (bitmap == null) { //让线程池去执行任务,会限制线程的数量 threadPool.invoke(new Runnable() { @Override public void run() { LayoutParams lp = imageView.getLayoutParams(); //获取本地的图片的压缩图 Bitmap bm = ImageUtil.decodeLocalImage(imageLocalPath, lp.width, lp.height); if (bm != null) { //添加到一级缓存 addBitmapToLruCache(imageLocalPath, bm); ImgBeanHolder holder = new ImgBeanHolder(); holder.bitmap = mLruCache.get(imageLocalPath); holder.imageView = imageView; holder.path = imageLocalPath; Message message = Message.obtain(); message.obj = holder; h.sendMessage(message); } } }); } else { imageView.setImageBitmap(bitmap); } } /** * 往LruCache中添加一张图片 * * @param key * @param bitmap */ private void addBitmapToLruCache(String key, Bitmap bitmap) { if (mLruCache.get(key) == null) { if (bitmap != null) mLruCache.put(key, bitmap); } } /** * 几个信息的持有者,其实就是封装一下 */ private class ImgBeanHolder { Bitmap bitmap; ImageView imageView; String path; } }代码不长
几个工具类的介绍
ThreadPool介绍
/** * Created by cxj on 2016/5/5. * 线程池,可以执行任务,任务是以{@link Runnable}接口的形式 * 详情请看{@link ThreadPool#invoke(Runnable)} */ public class ThreadPool implements Runnable { /** * 执行任务完毕停止 */ private final int stopped_flag = 0; /** * 无任务停止 */ private final int stoppedWithOutTash_flag = 1; //私有化构造函数 private ThreadPool() { } private static ThreadPool threadPool; /** * 用于设置图片到控件上 */ private Handler h = new Handler() { @Override public void handleMessage(Message msg) { int what = msg.what; switch (what) { case stopped_flag: //如果是停止了 currentThreadNum--; startThread(); break; case stoppedWithOutTash_flag: currentThreadNum--; break; } } }; public static ThreadPool getInstance() { if (threadPool == null) { threadPool = new ThreadPool(); } return threadPool; } /** * 最大的线程数量 */ private int maxThreadNum = 3; public void setMaxThreadNum(int maxThreadNum) { if (maxThreadNum < 1) { maxThreadNum = 1; } this.maxThreadNum = maxThreadNum; } /** * 当前的线程 */ private int currentThreadNum = 0; /** * 只留的任务的集合 */ private Vector<Runnable> runnables = new Vector<Runnable>(); /** * 执行一个任务 * * @param runnable */ public void invoke(Runnable runnable) { runnables.add(runnable); startThread(); } /** * 启动线程 */ private void startThread() { //如果还没有达到最大的线程数量 if (currentThreadNum < maxThreadNum) { currentThreadNum++; Thread t = new Thread(this); t.start(); } } /** * 获取一个任务 * * @return */ @Nullable private synchronized Runnable getTask() { if (runnables.size() > 0) { return runnables.remove(0); } return null; } /** * 非UI线程执行 */ @Override public void run() { //获取一个任务 Runnable task = getTask(); if (task == null) { //如果没有任务,直接返回 //发送停止的信息 h.sendEmptyMessage(stoppedWithOutTash_flag); return; } //执行任务 task.run(); //发送停止的信息 h.sendEmptyMessage(stopped_flag); } }这个明显是可以后续优化升级的,但是这里的情况已经适用了
EBus介绍
//发送纤细让Activity加载指定的文件夹中的图片 EBus.postEvent(SelectLocalImageAct.TAG, "loadImageByFolderPath", data.get(position));还记得我们的文件夹的选择是在哪里的么?