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

   

一、目标

  1. 1、尽可能避免内存溢出
  2. A、根据图片的显示大小去压缩图片
  3. B、使用缓存对图片进行管理(LruCache
  4. 2、保证用户操作UI尽可能的流畅
  5. getView()方法中尽量不做耗时操作(异步显示 + 回调显示)
  6. 3、用户预期显示的图片必须尽可能快的显示(图片加载策略的选择:LIFO后进先出 / FIFO先进先出),我们采用采用LIFO(后进先出的策略)

二、思路

1、图片的加载在Adapter的getView()方法中执行,我们根据一个图片的URL到LruCache缓存中寻找Bitmap图片,如果找到则返回图片,如果找不到,则会根据URL产生一个Task并放到TaskQueue中,同时发送一个通知提醒后台轮询线程,轮询线程从TaskQueue中取出一个Task到线程池去执行(执行的是Task的run()方法,具体为:获得图片显示的实际大小;使用Options对图片进行压缩;加载图片且放入LruCache)。我们需要在ImageLoader类中用到:LruCache缓存、线程池、任务列表TaskQueue、后台轮询线程、与轮询线程绑定的Handler和UI线程的Handler

2、具体的实现:Handler + Looper + Message(Android异步消息处理框架)。当我们用Handler发送消息时,会把信息放到MessageQueue中,轮询线程会取出这条消息,交给Handler的handleMessage()方法进行处理

3、后台轮询线程(Thread)不断访问任务队列(LinkList<Runnable>),如果任务队列中有加载图片的任务 (Runnable),就通过Handler发消息给线程池(ExecuterService),让线程池拿出一个子线程,然后根据调度任务的策略 (LIFO)从任务队列中取出一个任务去完成图片的获取,因为图片是异步的在子线程中获取到的,不能直接显示,所以需要通过一个UI相关的Handler把图片对象发送到UI线程中,最后完成图片的显示。

三、代码和解释

(一)目录结构图如下:

(二)代码和解释(解释都在代码的注释中):

MainActivity的布局文件activity_main.xml中的代码:

  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="fill_parent"
  5. android:background="@color/cl_white">
  6.  
  7. <RelativeLayout
  8. android:layout_width="match_parent"
  9. android:layout_height="50.0dip"
  10. android:background="#ee000000">
  11.  
  12. <ImageView
  13. android:id="@+id/position_main_iv_icon"
  14. android:layout_width="35.0dip"
  15. android:layout_height="35.0dip"
  16. android:layout_centerVertical="true"
  17. android:layout_marginLeft="10.0dip"
  18. android:src="@mipmap/ic_launcher" />
  19.  
  20. <TextView
  21. style="@style/Style_Main_TextView"
  22. android:layout_marginLeft="10.0dip"
  23. android:layout_toRightOf="@+id/position_main_iv_icon"
  24. android:text="图片选择器" />
  25. </RelativeLayout>
  26.  
  27. <RelativeLayout
  28. android:id="@+id/find_main_rl_bottomlayout"
  29. android:layout_width="match_parent"
  30. android:layout_height="50.0dip"
  31. android:layout_alignParentBottom="true"
  32. android:background="#ee000000">
  33.  
  34. <TextView
  35. android:id="@+id/find_main_tv_toall"
  36. style="@style/Style_Main_TextView"
  37. android:layout_marginLeft="10.0dip"
  38. android:text="所有图片" />
  39.  
  40. <TextView
  41. android:id="@+id/find_main_tv_num"
  42. style="@style/Style_Main_TextView"
  43. android:layout_alignParentRight="true"
  44. android:layout_marginRight="10.0dip"
  45. android:text="共100张" />
  46. </RelativeLayout>
  47.  
  48. <GridView
  49. android:id="@+id/find_main_gv_images"
  50. android:layout_width="fill_parent"
  51. android:layout_height="fill_parent"
  52. android:layout_marginBottom="50.0dip"
  53. android:layout_marginTop="50.0dip"
  54. android:cacheColorHint="@android:color/transparent"
  55. android:horizontalSpacing="3.0dip"
  56. android:listSelector="@android:color/transparent"
  57. android:numColumns="3"
  58. android:stretchMode="columnWidth"
  59. android:verticalSpacing="3.0dip" />
  60.  
  61. </RelativeLayout>

MainActivity.java中的代码:

  1. package com.itgungnir.activity;
  2.  
  3. import android.app.Activity;
  4. import android.app.ProgressDialog;
  5. import android.content.ContentResolver;
  6. import android.database.Cursor;
  7. import android.net.Uri;
  8. import android.os.Bundle;
  9. import android.os.Environment;
  10. import android.os.Handler;
  11. import android.os.Message;
  12. import android.provider.MediaStore;
  13. import android.view.View;
  14. import android.view.WindowManager;
  15. import android.widget.GridView;
  16. import android.widget.PopupWindow;
  17. import android.widget.RelativeLayout;
  18. import android.widget.TextView;
  19. import android.widget.Toast;
  20.  
  21. import com.itgungnir.entity.FolderModel;
  22. import com.itgungnir.tools.ImageAdapter;
  23. import com.itgungnir.view.DirsPopWindow;
  24.  
  25. import java.io.File;
  26. import java.io.FilenameFilter;
  27. import java.util.ArrayList;
  28. import java.util.Arrays;
  29. import java.util.HashSet;
  30. import java.util.List;
  31. import java.util.Set;
  32.  
  33. public class MainActivity extends Activity {
  34. private GridView imageGrid;
  35. private TextView dirName;
  36. private TextView dirCount;
  37. private ProgressDialog progressDialog; // 加载图片时出现的加载对话框
  38. private DirsPopWindow popWindow; // 可弹出的目录菜单
  39. private RelativeLayout bottomLayout;
  40.  
  41. private List<String> imageList; // 图片的数据集
  42. private File currentDir; // 当前所在的文件目录
  43. private int totalCount; // 显示dirCount中的数据
  44. private List<FolderModel> folderModels = new ArrayList<FolderModel>();
  45. private ImageAdapter imageAdapter;
  46.  
  47. private static final int DATA_LOADED = 0x110;
  48.  
  49. private Handler handler = new Handler() {
  50. @Override
  51. public void handleMessage(Message msg) {
  52. if (msg.what == DATA_LOADED) {
  53. progressDialog.dismiss();
  54. bindDataToView();
  55.  
  56. initDirsPopWindow();
  57. }
  58. }
  59. };
  60.  
  61. @Override
  62. protected void onCreate(Bundle savedInstanceState) {
  63. super.onCreate(savedInstanceState);
  64. setContentView(R.layout.activity_main);
  65. initView();
  66. initData();
  67. initEvent();
  68. }
  69.  
  70. /**
  71. * 初始化弹出菜单
  72. */
  73. private void initDirsPopWindow() {
  74. popWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
  75. @Override
  76. public void onDismiss() {
  77. lightOn();
  78. }
  79. });
  80. }
  81.  
  82. /**
  83. * 弹出窗口消失之后,需要将后面的图片列表变亮
  84. */
  85. private void lightOn() {
  86. WindowManager.LayoutParams lp = getWindow().getAttributes();
  87. lp.alpha = 1.0f;
  88. getWindow().setAttributes(lp);
  89. }
  90.  
  91. /**
  92. * 在现实PopUpWindow之后将后面的图片列表置黑
  93. */
  94. private void lightOff() {
  95. WindowManager.LayoutParams lp = getWindow().getAttributes();
  96. lp.alpha = 0.3f;
  97. getWindow().setAttributes(lp);
  98. }
  99.  
  100. /**
  101. * 绑定数据到View中
  102. */
  103. private void bindDataToView() {
  104. if (currentDir == null) {
  105. Toast.makeText(MainActivity.this, "未扫描到任何图片!", Toast.LENGTH_SHORT).show();
  106. return;
  107. }
  108. imageList = Arrays.asList(currentDir.list());
  109. imageAdapter = new ImageAdapter(MainActivity.this, imageList, currentDir.getAbsolutePath());
  110. imageGrid.setAdapter(imageAdapter);
  111.  
  112. dirCount.setText(totalCount + "");
  113. dirName.setText(currentDir.getName());
  114. }
  115.  
  116. /**
  117. * 初始化数据(利用ContentProvider扫描手机中的所有图片)
  118. */
  119. private void initData() {
  120. if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
  121. Toast.makeText(MainActivity.this, "当前存储卡不可用!", Toast.LENGTH_SHORT).show();
  122. return;
  123. }
  124. progressDialog = ProgressDialog.show(MainActivity.this, null, "正在加载...");
  125. new Thread() {
  126. @Override
  127. public void run() {
  128. /**
  129. * 初始化FolderModel,为PopUpWindow做准备
  130. */
  131. Uri imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
  132. ContentResolver cr = MainActivity.this.getContentResolver();
  133. Cursor cursor = cr.query(imageUri, null, MediaStore.Images.Media.MIME_TYPE + " = ? or " + MediaStore.Images.Media.MIME_TYPE + " = ? ",
  134. new String[]{"image/jpeg", "image/png"}, MediaStore.Images.Media.DATE_MODIFIED);
  135. Set<String> dirPaths = new HashSet<String>();
  136. while (cursor.moveToNext()) {
  137. String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); // 获取图片的路径
  138. File parentFile = new File(path).getParentFile(); // 获取该图片所在的父路径名
  139. if (parentFile == null) {
  140. continue;
  141. }
  142. String dirPath = parentFile.getAbsolutePath();
  143. FolderModel folder = null;
  144. // 放置重复扫描
  145. if (dirPaths.contains(dirPath)) {
  146. continue;
  147. } else {
  148. dirPaths.add(dirPath);
  149. folder = new FolderModel();
  150. folder.setDir(dirPath);
  151. folder.setFirstImgPath(path);
  152. }
  153. if (parentFile.list() == null) {
  154. continue;
  155. }
  156. int picSize = parentFile.list(new FilenameFilter() {
  157. @Override
  158. public boolean accept(File dir, String filename) {
  159. if (filename.endsWith(".jpg") || filename.endsWith(".jpeg") || filename.endsWith(".png")) {
  160. return true;
  161. }
  162. return false;
  163. }
  164. }).length;
  165. folder.setCount(picSize);
  166. folderModels.add(folder);
  167. if (picSize > totalCount) {
  168. totalCount = picSize;
  169. currentDir = parentFile;
  170. }
  171. }
  172. cursor.close();
  173. // 扫描完成,释放临时变量的内存
  174. dirPaths = null;
  175. handler.sendEmptyMessage(DATA_LOADED); // 通知Handler扫描图片完成
  176. }
  177. }.start();
  178. }
  179.  
  180. /**
  181. * 初始化事件
  182. */
  183. private void initEvent() {
  184. bottomLayout.setOnClickListener(new View.OnClickListener() {
  185. @Override
  186. public void onClick(View v) {
  187. popWindow.setAnimationStyle(R.style.Style_PopWindow_Anim);
  188. popWindow.showAsDropDown(bottomLayout, 0, 0);
  189. lightOff();
  190. }
  191. });
  192.  
  193. popWindow.setOnDirSelectListener(new DirsPopWindow.OnDirSelectListener() {
  194. @Override
  195. public void onDirSelected(FolderModel folder) {
  196. currentDir = new File(folder.getDir());
  197. imageList = Arrays.asList(currentDir.list(new FilenameFilter() {
  198. @Override
  199. public boolean accept(File dir, String filename) {
  200. if (filename.endsWith(".jpg") || filename.endsWith(".jpeg") || filename.endsWith(".png")) {
  201. return true;
  202. }
  203. return false;
  204. }
  205. }));
  206. imageAdapter = new ImageAdapter(MainActivity.this, imageList, currentDir.getAbsolutePath());
  207. imageGrid.setAdapter(imageAdapter);
  208. dirCount.setText(imageList.size() + "");
  209. dirName.setText(folder.getName());
  210.  
  211. popWindow.dismiss();
  212. }
  213. });
  214. }
  215.  
  216. /**
  217. * 初始化控件
  218. */
  219. private void initView() {
  220. imageGrid = (GridView) findViewById(R.id.find_main_gv_images);
  221. dirName = (TextView) findViewById(R.id.find_main_tv_toall);
  222. dirCount = (TextView) findViewById(R.id.find_main_tv_num);
  223. bottomLayout = (RelativeLayout) findViewById(R.id.find_main_rl_bottomlayout);
  224. popWindow = new DirsPopWindow(MainActivity.this, folderModels);
  225. }
  226.  
  227. @Override
  228. protected void onDestroy() {
  229. progressDialog.dismiss();
  230. super.onDestroy();
  231. }
  232. }

图片压缩加载类ImageLoader.java中的代码:

  1. package com.itgungnir.tools;
  2.  
  3. import java.lang.reflect.Field;
  4. import java.util.LinkedList;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.Semaphore;
  8.  
  9. import android.graphics.Bitmap;
  10. import android.graphics.BitmapFactory;
  11. import android.os.Handler;
  12. import android.os.Looper;
  13. import android.os.Message;
  14. import android.support.v4.util.LruCache;
  15. import android.util.DisplayMetrics;
  16. import android.util.Log;
  17. import android.view.ViewGroup.LayoutParams;
  18. import android.widget.ImageView;
  19.  
  20. /**
  21. * ********图片加载类*********
  22. * ***思路:图片的加载在Adapter的getView()方法中执行,我们根据一个图片的URL到LruCache缓存中寻找Bitmap图片,如果找到则返回图片,
  23. * 如果找不到,则会根据URL产生一个Task并放到TaskQueue中,同时发送一个通知提醒后台轮询线程,轮询线程从TaskQueue中取出一个Task到线程池去执行(执行的是Task的run()方法,
  24. * 具体为:获得图片显示的实际大小;使用Options对图片进行压缩;加载图片且放入LruCache)
  25. * ***核心:Handler + Looper + Message(Android异步消息处理框架)
  26. * 当我们用Handler发送消息时,会把信息放到MessageQueue中,轮询线程会取出这条消息,交给Handler的handleMessage()方法进行处理
  27. */
  28. public class ImageLoader {
  29. private static ImageLoader mInstance; // 实例
  30. private LruCache<String, Bitmap> mLruCache; // 图片缓存的核心类
  31. private ExecutorService mThreadPool; // 线程池
  32. private static final int DEFAULT_THREAD_COUNT = 1; // 线程池的线程数量,默认为1
  33. private Type mType = Type.LIFO; // 任务队列的默认调度方式
  34. private LinkedList<Runnable> taskQueue; // 任务队列
  35. private Thread mPoolThread; // 后台轮询线程
  36. private Handler mPoolThreadHandler; // 与后台轮询线程绑定的Handler
  37. private Handler uiHandler; // 运行在UI线程的handler,用于给ImageView设置图片
  38. /**
  39. * 引入一个值为1的信号量,防止mPoolThreadHander未初始化完成
  40. * Semaphore的作用是限制某一资源的同步访问
  41. * 可以把Semaphore理解成一个可以容纳N个人的房间,如果人数没有达到N就可以进去,如果人满了,就要等待有人出来才可以进去
  42. * 在addTask()方法中需要用到后台轮询线程poolThreadHandler,但存在线程同步问题,
  43. * 即addTask()方法可能在poolThreadHandler初始化之前就被调用了,所以我们需要定义这样一个“信号量”来调控线程同步
  44. */
  45. private volatile Semaphore mPoolThreadHandlerSemaphore = new Semaphore(0); // 控制addTask()方法在mPoolThreadHandler吃实话之后才能调用
  46. private volatile Semaphore mPoolSemaphore; // 引入一个值为1的信号量,由于线程池内部也有一个阻塞线程,防止加入任务的速度过快,使LIFO效果不明显
  47.  
  48. /**
  49. * 图片加载的策略(FIFO先进先出、LIFO后进先出)
  50. */
  51. public enum Type {
  52. FIFO, LIFO
  53. }
  54.  
  55. /**
  56. * 构造函数
  57. * 由于ImageLoader中需要使用LruCache来缓存图片,需要占据较大的空间,所以整个项目中只需要一个ImageLoader即可(需要使用单例模式)
  58. * 因此我们把构造方法设为private权限,防止用户new出实例
  59. *
  60. * @param threadCount 任务队列中默认线程数
  61. * @param type 图片加载策略(先进先出/后进先出)
  62. */
  63. private ImageLoader(int threadCount, Type type) {
  64. init(threadCount, type);
  65. }
  66.  
  67. /**
  68. * 单例获得该实例对象(无参数,按照默认值进行初始化)
  69. */
  70. public static ImageLoader getInstance() {
  71. if (mInstance == null) {
  72. synchronized (ImageLoader.class) {
  73. if (mInstance == null) {
  74. mInstance = new ImageLoader(DEFAULT_THREAD_COUNT, Type.LIFO);
  75. }
  76. }
  77. }
  78. return mInstance;
  79. }
  80.  
  81. /**
  82. * 单例获得该实例对象(有参数,用户可以根据实际需要对ImageLoader进行实例化)
  83. */
  84. public static ImageLoader getInstance(int threadCount, Type type) {
  85. if (mInstance == null) {
  86. synchronized (ImageLoader.class) {
  87. if (mInstance == null) {
  88. mInstance = new ImageLoader(threadCount, type);
  89. }
  90. }
  91. }
  92. return mInstance;
  93. }
  94.  
  95. /**
  96. * 完成成员变量的初始化操作
  97. */
  98. private void init(int threadCount, Type type) {
  99. /**
  100. * 初始化后台轮询线程(使用Looper、Handler、Message实现)
  101. */
  102. mPoolThread = new Thread() {
  103. @Override
  104. public void run() {
  105. Looper.prepare();
  106. mPoolThreadHandler = new Handler() {
  107. @Override
  108. public void handleMessage(Message msg) {
  109. mThreadPool.execute(getTask()); // 线程池取出一个任务去执行
  110. try {
  111. mPoolSemaphore.acquire();
  112. } catch (InterruptedException e) {
  113. }
  114. }
  115. };
  116. mPoolThreadHandlerSemaphore.release(); // 释放一个信号量
  117. Looper.loop(); // 开始无限循环
  118. }
  119. };
  120. mPoolThread.start();
  121. /**
  122. * 初始化LruCache
  123. */
  124. int maxMemory = (int) Runtime.getRuntime().maxMemory(); // 获取应用程序最大可用内存
  125. int cacheSize = maxMemory / 8; // 设置缓存的存储空间是总空间的1/8
  126. mLruCache = new LruCache<String, Bitmap>(cacheSize) {
  127. @Override
  128. protected int sizeOf(String key, Bitmap value) {
  129. return value.getRowBytes() * value.getHeight(); // getRowBytes()获取到图片每行有多少字节,乘以图片的高度就是图片占据的内存
  130. }
  131. };
  132. /**
  133. * 初始化其他
  134. */
  135. mThreadPool = Executors.newFixedThreadPool(threadCount); // 初始化线程池threadPool
  136. mPoolSemaphore = new Semaphore(threadCount); // 初始化线程池(消息队列)信号量
  137. taskQueue = new LinkedList<Runnable>(); // 初始化任务队列taskQueue
  138. mType = type == null ? Type.LIFO : type; // 同步Type
  139. }
  140.  
  141. /**
  142. * 根据路径path找到对应的图片并异步加载到主界面中
  143. *
  144. * @param path 图片的路径
  145. * @param imageView 目标的ImageView(图片将要被加载到的ImageView)
  146. */
  147. public void loadImage(final String path, final ImageView imageView) {
  148. imageView.setTag(path); // 将PATH设置为imageView的TAG,方便在主线程中对比、设置图片
  149. // UI线程
  150. if (uiHandler == null) {
  151. uiHandler = new Handler() {
  152. @Override
  153. public void handleMessage(Message msg) {
  154. ImgBeanHolder holder = (ImgBeanHolder) msg.obj;
  155. ImageView imageView = holder.imageView;
  156. Bitmap bm = holder.bitmap;
  157. String path = holder.path;
  158. // 将path与imageView的tag进行比较,如果相同,则为imageView设置图片
  159. if (imageView.getTag().toString().equals(path)) {
  160. imageView.setImageBitmap(bm);
  161. }
  162. }
  163. };
  164. }
  165.  
  166. Bitmap bm = getBitmapFromLruCache(path); // 根据路径Path在缓存中获取Bitmap
  167. if (bm != null) { // 如果这张图片存在于缓存中,则通知UI线程更新图片
  168. ImgBeanHolder holder = new ImgBeanHolder();
  169. holder.bitmap = bm;
  170. holder.imageView = imageView;
  171. holder.path = path;
  172. Message message = Message.obtain();
  173. message.obj = holder;
  174. uiHandler.sendMessage(message);
  175. } else { // 如果这张图片没有存在于缓存中,则添加一个新的任务到任务队列中
  176. addTask(new Runnable() { // 添加一个任务到任务队列中
  177. @Override
  178. public void run() {
  179. /**
  180. * 加载图片
  181. */
  182. // 压缩图片:1、获取图片需要显示的大小
  183. ImageSize imageSize = getImageViewSize(imageView);
  184. // 压缩图片:2、压缩图片
  185. int reqWidth = imageSize.width;
  186. int reqHeight = imageSize.height;
  187. Bitmap bm = decodeSampledBitmapFromResource(path, reqWidth, reqHeight);
  188. // 压缩图片:3、将图片加入到缓存
  189. addBitmapToLruCache(path, bm);
  190. // 将上面操作获取到的数据封装到ImgBeanHolder实体对象中,通知UI线程处理
  191. ImgBeanHolder holder = new ImgBeanHolder();
  192. holder.bitmap = getBitmapFromLruCache(path);
  193. holder.imageView = imageView;
  194. holder.path = path;
  195. Message message = Message.obtain();
  196. message.obj = holder;
  197. uiHandler.sendMessage(message);
  198. mPoolSemaphore.release(); // 为线程池释放一个信号量
  199. }
  200. });
  201. }
  202. }
  203.  
  204. /**
  205. * 添加一个任务到任务队列
  206. * synchronized是为了避免多个线程同时到达这个方法中申请信号量而发生死锁
  207. */
  208. private synchronized void addTask(Runnable runnable) {
  209. /**
  210. * 请求资源信号量,避免死锁
  211. * 如果还没有对mPoolThreadHandler进行初始化,则默认的房间里可以容纳0个人,所以如果此时addTask()方法请求资源会被阻塞
  212. * 通过这个信号量控制addTask()方法必须在mPoolThreadHandler初始化之后才调用
  213. */
  214. try {
  215. if (mPoolThreadHandler == null)
  216. mPoolThreadHandlerSemaphore.acquire(); // 如果当前mPoolThreadHandler还没有初始化,则该线程阻塞(等待),直到mPoolThreadHandler初始化
  217. } catch (InterruptedException e) {
  218. e.printStackTrace();
  219. }
  220. taskQueue.add(runnable); // 添加Runnable任务到任务队列
  221. mPoolThreadHandler.sendEmptyMessage(0x110); // 发送一个通知,通知后台轮询线程,0x110是一个随意的值
  222. }
  223.  
  224. /**
  225. * 从任务队列中取出一个任务
  226. * 根据为ImageLoader实例设定的图片加载策略决定是取出最后一个还是取出第一个
  227. */
  228. private synchronized Runnable getTask() {
  229. if (mType == Type.FIFO) {
  230. return taskQueue.removeFirst();
  231. } else if (mType == Type.LIFO) {
  232. return taskQueue.removeLast();
  233. }
  234. return null;
  235. }
  236.  
  237. /**
  238. * 根据ImageView获得适当的压缩的目标宽和高
  239. */
  240. private ImageSize getImageViewSize(ImageView imageView) {
  241. /**
  242. * 获取ImageView的LayoutParams
  243. */
  244. final DisplayMetrics metrics = imageView.getContext().getResources().getDisplayMetrics();
  245. final LayoutParams lp = imageView.getLayoutParams();
  246. /**
  247. * 定义ImageView显示的宽度
  248. */
  249. int width = lp.width == LayoutParams.WRAP_CONTENT ? 0 : imageView.getWidth(); // 获取ImageView的实际宽度
  250. if (width <= 0) // ImageView可能是刚new出来就来执行这个方法,所以还没有宽高值,只能通过在layout中声明的宽高值来赋值
  251. width = lp.width; // 获取ImageView在layout中声明的宽度
  252. if (width <= 0) // 在layout中设置的宽高值是WRAP_CONTENT或MATCH_PARENT,则还是取出0,这时我们就需要看ImageView有没有设置max值
  253. width = getImageViewFieldValue(imageView, "mMaxWidth"); // 通过反射获取ImageView的宽度最大值
  254. if (width <= 0) // 可能ImageView没有设置max值,因此我们只能设置ImageView的宽或高为屏幕的宽或高
  255. width = metrics.widthPixels;
  256. /**
  257. * 定义ImageView显示的高度(同宽度)
  258. */
  259. int height = lp.height == LayoutParams.WRAP_CONTENT ? 0 : imageView.getHeight();
  260. if (height <= 0) height = lp.height;
  261. if (height <= 0) height = getImageViewFieldValue(imageView, "mMaxHeight");
  262. if (height <= 0) height = metrics.heightPixels;
  263. /**
  264. * 将ImageView压缩后的宽和高封装到ImageSize实体类中返回
  265. */
  266. ImageSize imageSize = new ImageSize();
  267. imageSize.width = width;
  268. imageSize.height = height;
  269. return imageSize;
  270. }
  271.  
  272. /**
  273. * 从LruCache中获取一张图片,如果不存在就返回null
  274. */
  275. private Bitmap getBitmapFromLruCache(String key) {
  276. return mLruCache.get(key);
  277. }
  278.  
  279. /**
  280. * 将图片添加到LruCache缓存
  281. *
  282. * @param path 路径,先要判断图片是否已经在缓存中
  283. * @param bitmap 图片
  284. */
  285. private void addBitmapToLruCache(String path, Bitmap bitmap) {
  286. if (getBitmapFromLruCache(path) == null) {
  287. if (bitmap != null)
  288. mLruCache.put(path, bitmap);
  289. }
  290. }
  291.  
  292. /**
  293. * 根据图片需求的宽高和图片实际的宽高计算inSampleSize(图片压缩的比例,用于压缩图片)
  294. *
  295. * @param options 图片实际的宽和高
  296. * @param reqWidth 图片需要的宽度
  297. * @param reqHeight 图片需要的高度
  298. */
  299. private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
  300. int width = options.outWidth; // 原图片的宽度
  301. int height = options.outHeight; // 原图片的高度
  302. int inSampleSize = 1; // 缩放的比例
  303. if (width > reqWidth && height > reqHeight) {
  304. int widthRatio = Math.round((float) width / (float) reqWidth); // 计算出实际宽度和目标宽度的比率
  305. int heightRatio = Math.round((float) width / (float) reqWidth);
  306. inSampleSize = Math.max(widthRatio, heightRatio); // 取宽度缩放值和高度缩放值的较大值作为图片缩放比例
  307. }
  308. return inSampleSize;
  309. }
  310.  
  311. /**
  312. * 根据图片需要显示的宽和高对图片进行压缩
  313. *
  314. * @param path 图片的路径
  315. * @param reqWidth 图片显示的宽度
  316. * @param reqHeight 图片显示的高度
  317. */
  318. private Bitmap decodeSampledBitmapFromResource(String path, int reqWidth, int reqHeight) {
  319. final BitmapFactory.Options options = new BitmapFactory.Options();
  320. options.inJustDecodeBounds = true; // 如果该值设为true那么将不返回实际的bitmap,也不给其分配内存空间(避免内存溢出),但允许我们查询图片信息(包括图片大小信息)
  321. BitmapFactory.decodeFile(path, options); // 经过这行代码,options就获得了图片实际的宽和高
  322. options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 调用上面定义的方法计算inSampleSize值(图片压缩的比例)
  323. options.inJustDecodeBounds = false; // 设置可以取出图片
  324. Bitmap bitmap = BitmapFactory.decodeFile(path, options); // 使用获取到的inSampleSize值再次解析图片,取出Bitmap
  325. return bitmap;
  326. }
  327.  
  328. /**
  329. * 存放图片的Bitmap源、ImageView和图片路径的实体类
  330. */
  331. private class ImgBeanHolder {
  332. Bitmap bitmap;
  333. ImageView imageView;
  334. String path;
  335. }
  336.  
  337. /**
  338. * 存放ImageView显示的宽高的实体类
  339. */
  340. private class ImageSize {
  341. int width; // ImageView显示的宽度
  342. int height; // ImageView显示的高度
  343. }
  344.  
  345. /**
  346. * 通过反射获取某个Object的fieldName属性对应的值(本程序中是通过反射获得ImageView设置的最大宽度和高度)
  347. */
  348. private static int getImageViewFieldValue(Object object, String fieldName) {
  349. int value = 0;
  350. try {
  351. Field field = ImageView.class.getDeclaredField(fieldName);
  352. field.setAccessible(true);
  353. int fieldValue = (Integer) field.get(object);
  354. if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
  355. value = fieldValue;
  356. }
  357. } catch (Exception e) {
  358. e.printStackTrace();
  359. }
  360. return value;
  361. }
  362. }

图片文件夹实体类FolderModel.java中的代码:

  1. package com.itgungnir.entity;
  2.  
  3. /**
  4. * PopUpWindow对应的Bean类
  5. */
  6. public class FolderModel {
  7. private String dir; // 图片的文件夹的路径
  8. private String firstImgPath; // 第一张图片的路径
  9. private String name; // 文件夹的名称
  10. private int count; // 文件夹中图片的数量
  11.  
  12. public String getFirstImgPath() {
  13. return firstImgPath;
  14. }
  15.  
  16. public void setFirstImgPath(String firstImgPath) {
  17. this.firstImgPath = firstImgPath;
  18. }
  19.  
  20. public String getName() {
  21. return name;
  22. }
  23.  
  24. public int getCount() {
  25. return count;
  26. }
  27.  
  28. public void setCount(int count) {
  29. this.count = count;
  30. }
  31.  
  32. public String getDir() {
  33. return dir;
  34. }
  35.  
  36. public void setDir(String dir) {
  37. this.dir = dir;
  38. int lastIndex = this.dir.lastIndexOf("/");
  39. this.name = this.dir.substring(lastIndex);
  40. }
  41. }

主界面GridView的适配器类ImageAdapter.java中的代码:

  1. package com.itgungnir.tools;
  2.  
  3. import android.content.Context;
  4. import android.graphics.Color;
  5. import android.view.LayoutInflater;
  6. import android.view.View;
  7. import android.view.ViewGroup;
  8. import android.widget.BaseAdapter;
  9. import android.widget.ImageButton;
  10. import android.widget.ImageView;
  11.  
  12. import com.itgungnir.activity.R;
  13.  
  14. import java.util.HashSet;
  15. import java.util.List;
  16. import java.util.Set;
  17.  
  18. /**
  19. * 主界面MainActivity中的GridView的适配器
  20. */
  21. public class ImageAdapter extends BaseAdapter {
  22. private static final Set<String> selectedImages = new HashSet<String>(); // 存储选中的图片的路径
  23.  
  24. private String dirPath; // GridView中图片的父路径
  25. private List<String> imagePaths; // GridView中显示的图片的路径(s)
  26. private LayoutInflater inflater;
  27.  
  28. public ImageAdapter(Context context, List<String> data, String dirPath) {
  29. this.dirPath = dirPath;
  30. this.imagePaths = data;
  31. inflater = LayoutInflater.from(context);
  32. }
  33.  
  34. @Override
  35. public int getCount() {
  36. return imagePaths.size();
  37. }
  38.  
  39. @Override
  40. public Object getItem(int position) {
  41. return imagePaths.get(position);
  42. }
  43.  
  44. @Override
  45. public long getItemId(int position) {
  46. return position;
  47. }
  48.  
  49. @Override
  50. public View getView(final int position, View convertView, ViewGroup parent) {
  51. final ViewHolder viewHolder;
  52. if (convertView == null) {
  53. convertView = inflater.inflate(R.layout.sideworks_griditem, parent, false);
  54. viewHolder = new ViewHolder();
  55. viewHolder.imageView = (ImageView) convertView.findViewById(R.id.find_griditem_iv_image);
  56. viewHolder.checkBtn = (ImageButton) convertView.findViewById(R.id.find_griditem_ib_check);
  57. convertView.setTag(viewHolder);
  58. } else {
  59. viewHolder = (ViewHolder) convertView.getTag();
  60. }
  61. // 重置状态
  62. viewHolder.imageView.setImageResource(R.mipmap.img_pictures_notfound);
  63. viewHolder.checkBtn.setImageResource(R.mipmap.img_picture_unselected);
  64. viewHolder.imageView.setColorFilter(null);
  65. // 加载图片
  66. ImageLoader.getInstance(3, ImageLoader.Type.LIFO).loadImage(dirPath + "/" + imagePaths.get(position), viewHolder.imageView);
  67.  
  68. /**
  69. * 为GridView的Item设置点击事件
  70. */
  71. viewHolder.imageView.setOnClickListener(new View.OnClickListener() {
  72. @Override
  73. public void onClick(View v) {
  74. String imagePath = dirPath + "/" + imagePaths.get(position);
  75. if (selectedImages.contains(imagePath)) { // 如果图片已经被选择,则清楚选择状态
  76. selectedImages.remove(imagePath);
  77. viewHolder.imageView.setColorFilter(null);
  78. viewHolder.checkBtn.setImageResource(R.mipmap.img_picture_unselected);
  79. } else { // 如果图片没有被选择,则选择图片
  80. selectedImages.add(imagePath);
  81. viewHolder.imageView.setColorFilter(Color.parseColor("#77000000")); // 设置图片上面覆盖一层透明的黑色蒙版
  82. viewHolder.checkBtn.setImageResource(R.mipmap.img_pictures_selected); // 设置多选按钮为选中
  83. }
  84. }
  85. });
  86.  
  87. return convertView;
  88. }
  89.  
  90. private class ViewHolder {
  91. ImageView imageView;
  92. ImageButton checkBtn;
  93. }
  94. }

主界面GridView的子View布局sideworks_griditem.xml文件中的代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5.  
  6. <ImageView
  7. android:id="@+id/find_griditem_iv_image"
  8. android:layout_width="match_parent"
  9. android:layout_height="100.0dip"
  10. android:scaleType="centerCrop"
  11. android:src="@mipmap/img_pictures_notfound" />
  12.  
  13. <ImageButton
  14. android:id="@+id/find_griditem_ib_check"
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. android:layout_alignParentRight="true"
  18. android:layout_marginRight="3.0dip"
  19. android:layout_marginTop="3.0dip"
  20. android:background="@null"
  21. android:clickable="false"
  22. android:src="@mipmap/img_picture_unselected" />
  23.  
  24. </RelativeLayout>

弹出窗口DirsPopWindow.java中的代码:

  1. package com.itgungnir.view;
  2.  
  3. import android.content.Context;
  4. import android.graphics.drawable.BitmapDrawable;
  5. import android.util.DisplayMetrics;
  6. import android.view.LayoutInflater;
  7. import android.view.MotionEvent;
  8. import android.view.View;
  9. import android.view.ViewGroup;
  10. import android.view.WindowManager;
  11. import android.widget.AdapterView;
  12. import android.widget.ArrayAdapter;
  13. import android.widget.ImageView;
  14. import android.widget.ListView;
  15. import android.widget.PopupWindow;
  16. import android.widget.TextView;
  17.  
  18. import com.itgungnir.activity.R;
  19. import com.itgungnir.entity.FolderModel;
  20. import com.itgungnir.tools.ImageLoader;
  21.  
  22. import java.util.List;
  23.  
  24. public class DirsPopWindow extends PopupWindow {
  25. private int width; // PopUpWindow的宽度
  26. private int height; // PopUpWindow的高度
  27. private View convertView; // 代表PopUpWindow
  28. private ListView dirList; // 显示目录信息的ListView
  29. private List<FolderModel> datas; // ListView中显示的信息的列表
  30.  
  31. public OnDirSelectListener listener;
  32.  
  33. public interface OnDirSelectListener {
  34. void onDirSelected(FolderModel folder);
  35. }
  36.  
  37. public void setOnDirSelectListener(OnDirSelectListener listener) {
  38. this.listener = listener;
  39. }
  40.  
  41. public DirsPopWindow(Context context, List<FolderModel> datas) {
  42. calculateWidthAndHeight(context);
  43. convertView = LayoutInflater.from(context).inflate(R.layout.sideworks_popwindow, null);
  44. this.datas = datas;
  45. setContentView(convertView);
  46. setWidth(this.width);
  47. setHeight(this.height);
  48. setFocusable(true);
  49. setTouchable(true);
  50. setOutsideTouchable(true); // 外面可以点击
  51. setBackgroundDrawable(new BitmapDrawable()); // 点击外面的区域会使PopUpWindow消失
  52. // 点击外部的事件(让PopUpWindow消失)
  53. setTouchInterceptor(new View.OnTouchListener() {
  54. @Override
  55. public boolean onTouch(View v, MotionEvent event) {
  56. if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
  57. dismiss();
  58. return true;
  59. }
  60. return false;
  61. }
  62. });
  63.  
  64. initView(context);
  65. initEvent();
  66. }
  67.  
  68. private void initView(Context context) {
  69. dirList = (ListView) convertView.findViewById(R.id.find_popwindow_lv_dirs);
  70. dirList.setAdapter(new DirAdapter(context, datas));
  71. }
  72.  
  73. private void initEvent() {
  74. dirList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
  75. @Override
  76. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  77. if (listener != null) {
  78. listener.onDirSelected(datas.get(position));
  79. }
  80. }
  81. });
  82. }
  83.  
  84. /**
  85. * 计算PopUpWindow的宽度和高度
  86. */
  87. private void calculateWidthAndHeight(Context context) {
  88. // 获取屏幕的宽和高
  89. WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  90. DisplayMetrics metrics = new DisplayMetrics();
  91. wm.getDefaultDisplay().getMetrics(metrics);
  92. // 为PopUpWindow设置宽和高
  93. this.width = metrics.widthPixels;
  94. this.height = (int) (metrics.heightPixels * 0.7);
  95. }
  96.  
  97. /**
  98. * PopUpWindow中ListView的适配器
  99. */
  100. private class DirAdapter extends ArrayAdapter<FolderModel> {
  101. private LayoutInflater inflater;
  102. private List<FolderModel> datas;
  103.  
  104. public DirAdapter(Context context, List<FolderModel> objects) {
  105. super(context, 0, objects);
  106. inflater = LayoutInflater.from(context);
  107. }
  108.  
  109. @Override
  110. public View getView(int position, View convertView, ViewGroup parent) {
  111. ViewHolder viewHolder = null;
  112. if (convertView == null) {
  113. viewHolder = new ViewHolder();
  114. convertView = inflater.inflate(R.layout.sideworks_popitem, parent, false);
  115. viewHolder.image = (ImageView) convertView.findViewById(R.id.find_popitem_iv_image);
  116. viewHolder.dirName = (TextView) convertView.findViewById(R.id.find_popitem_tv_dir);
  117. viewHolder.imgCount = (TextView) convertView.findViewById(R.id.find_popitem_tv_imgcount);
  118. convertView.setTag(viewHolder);
  119. } else {
  120. viewHolder = (ViewHolder) convertView.getTag();
  121. }
  122.  
  123. FolderModel folder = getItem(position);
  124. // 重置
  125. viewHolder.image.setImageResource(R.mipmap.img_pictures_notfound);
  126. // 回调加载
  127. ImageLoader.getInstance(3, ImageLoader.Type.LIFO).loadImage(folder.getFirstImgPath(), viewHolder.image);
  128. viewHolder.dirName.setText(folder.getName());
  129. viewHolder.imgCount.setText(folder.getCount() + "");
  130.  
  131. return convertView;
  132. }
  133.  
  134. private class ViewHolder {
  135. ImageView image;
  136. TextView dirName;
  137. TextView imgCount;
  138. }
  139. }
  140. }

弹出窗口的布局文件sideworks_popwindow.xml中的代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical">
  6.  
  7. <ListView
  8. android:id="@+id/find_popwindow_lv_dirs"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent"
  11. android:background="@color/cl_white"
  12. android:cacheColorHint="@color/cl_transparent"
  13. android:divider="#EEE3D9"
  14. android:dividerHeight="1.0dip" />
  15.  
  16. </LinearLayout>

弹出窗口中ListView的子View的布局sideworks_popitem.xml文件中的代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="120.0dip"
  5. android:background="@color/cl_white"
  6. android:padding="10.0dip">
  7.  
  8. <ImageView
  9. android:id="@+id/find_popitem_iv_image"
  10. android:layout_width="100.0dip"
  11. android:layout_height="100.0dip"
  12. android:layout_centerVertical="true"
  13. android:background="@mipmap/img_pic_dir_bg"
  14. android:contentDescription="@string/app_name"
  15. android:paddingBottom="17.0dip"
  16. android:paddingLeft="12.0dip"
  17. android:paddingRight="12.0dip"
  18. android:paddingTop="9.0dip"
  19. android:scaleType="fitXY" />
  20.  
  21. <LinearLayout
  22. android:layout_width="wrap_content"
  23. android:layout_height="wrap_content"
  24. android:layout_centerVertical="true"
  25. android:layout_marginLeft="20.0dip"
  26. android:layout_toRightOf="@+id/find_popitem_iv_image"
  27. android:orientation="vertical">
  28.  
  29. <TextView
  30. android:id="@+id/find_popitem_tv_dir"
  31. android:layout_width="wrap_content"
  32. android:layout_height="wrap_content"
  33. android:textColor="@color/cl_black"
  34. android:textSize="16.0sp" />
  35.  
  36. <TextView
  37. android:id="@+id/find_popitem_tv_imgcount"
  38. android:layout_width="wrap_content"
  39. android:layout_height="wrap_content"
  40. android:textColor="@android:color/darker_gray"
  41. android:textSize="14.0sp" />
  42. </LinearLayout>
  43.  
  44. <ImageView
  45. android:layout_width="wrap_content"
  46. android:layout_height="wrap_content"
  47. android:layout_alignParentRight="true"
  48. android:layout_centerVertical="true"
  49. android:src="@mipmap/img_dir_choosen" />
  50.  
  51. </RelativeLayout>

弹出窗口弹出的动画slide_up.xml文件中的代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <set xmlns:android="http://schemas.android.com/apk/res/android">
  3.  
  4. <translate
  5. android:duration="200"
  6. android:fromXDelta="0"
  7. android:fromYDelta="100%"
  8. android:toXDelta="0"
  9. android:toYDelta="0" />
  10.  
  11. </set>

弹出窗口隐藏的动画slide_down.xml文件中的代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <set xmlns:android="http://schemas.android.com/apk/res/android">
  3.  
  4. <translate
  5. android:duration="200"
  6. android:fromXDelta="0"
  7. android:fromYDelta="0"
  8. android:toXDelta="0"
  9. android:toYDelta="100%" />
  10.  
  11. </set>

样式文件styles.xml中的代码:

  1. <resources>
  2.  
  3. <!-- Base application theme. -->
  4. <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
  5. <!-- Customize your theme here. -->
  6. <item name="colorPrimary">@color/colorPrimary</item>
  7. <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
  8. <item name="colorAccent">@color/colorAccent</item>
  9. </style>
  10.  
  11. <style name="Style_Main_TextView">
  12. <item name="android:layout_width">wrap_content</item>
  13. <item name="android:layout_height">wrap_content</item>
  14. <item name="android:layout_centerVertical">true</item>
  15. <item name="android:textColor">@android:color/white</item>
  16. <item name="android:textSize">18.0sp</item>
  17. <item name="android:textStyle">bold</item>
  18. </style>
  19.  
  20. <style name="Style_PopWindow_Anim">
  21. <item name="android:windowEnterAnimation">@anim/slide_up</item>
  22. <item name="android:windowExitAnimation">@anim/slide_down</item>
  23. </style>
  24.  
  25. </resources>

清单文件AndroidMenifest.xml文件中的代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.itgungnir.activity">
  4.  
  5. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  6. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  7.  
  8. <application
  9. android:allowBackup="true"
  10. android:icon="@mipmap/ic_launcher"
  11. android:label="@string/app_name"
  12. android:supportsRtl="true"
  13. android:theme="@android:style/Theme.NoTitleBar">
  14. <activity android:name=".MainActivity">
  15. <intent-filter>
  16. <action android:name="android.intent.action.MAIN" />
  17.  
  18. <category android:name="android.intent.category.LAUNCHER" />
  19. </intent-filter>
  20. </activity>
  21. </application>
  22.  
  23. </manifest>

Android之仿微信图片选择器的更多相关文章

  1. [转]Android 超高仿微信图片选择器 图片该这么加载

    快速加载本地图片缩略图的方法: 原文地址:Android 超高仿微信图片选择器 图片该这么加载 其示例代码下载: 仿微信图片选择器 ImageLoader

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

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

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

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

  4. Android 高级UI设计笔记06:仿微信图片选择器(转载)

    仿微信图片选择器: 一.项目整体分析: 1. Android加载图片的3个目标: (1)尽可能的去避免内存溢出. a. 根据图片的显示大小去压缩图片 b. 使用缓存对我们图片进行管理(LruCache ...

  5. Android开发之高仿微信图片选择器

    记得刚开始做Andriod项目那会,经常会碰到一些上传图片的功能需求,特别是社交类的app,比如用户头像,说说配图,商品配图等功能都需要让我们到系统相册去选取图片,但官方却没有提供可以选取多张图片的相 ...

  6. Android高仿微信图片选择功能的PhotoPicker

    类似于微信修改头像的功能基本上每个app都会有,以前公司开发的项目就有修改头像的功能,但是用的Android系统自带的图片选择器.用Android系统的图片选择器有个好处就是稳定,不会有什么问题.但也 ...

  7. Android仿微信图片上传,可以选择多张图片,缩放预览,拍照上传等

    仿照微信,朋友圈分享图片功能 .可以进行图片的多张选择,拍照添加图片,以及进行图片的预览,预览时可以进行缩放,并且可以删除选中状态的图片 .很不错的源码,大家有需要可以下载看看 . 微信 微信 微信 ...

  8. android之使用GridView+仿微信图片上传功能

    由于工作要求最近在使用GridView完成图片的批量上传功能,我的例子当中包含仿微信图片上传.拍照.本地选择.相片裁剪等功能,如果有需要的朋友可以看一下,希望我的实际经验能对您有所帮助. 直接上图,下 ...

  9. Android开发技巧——定制仿微信图片裁剪控件

    拍照--裁剪,或者是选择图片--裁剪,是我们设置头像或上传图片时经常需要的一组操作.上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现. 背景 下面的需求都来自产品. 裁剪图片要像微信那样,拖动 ...

随机推荐

  1. postgresql 导出数据字典文档

    项目上需要整理目前数据库的数据字典文档.项目不规范,这种文档只要后期来补.这么多张表,每个字段都写到word文档里真心头大.就算前面写了个查询表结构的sql,但是最后整理到word里还是感觉有点麻烦. ...

  2. pitch yaw roll是什么

    虚拟现实 三维空间的右手笛卡尔坐标如图1所示. 图1 在航空中,pitch, yaw, roll如图2所示. pitch是围绕X轴旋转,也叫做俯仰角,如图3所示. yaw是围绕Y轴旋转,也叫偏航角,如 ...

  3. Windows批处理:自动开关程序

    公司有台14年组装的PC,时常无故重启,所以编写了个然并卵的批处理来测试稳定性. 打开程序.bat @echo off title Start Software color 2F : "C: ...

  4. 图像处理中任意核卷积(matlab中conv2函数)的快速实现。

    卷积其实是图像处理中最基本的操作,我们常见的一些算法比如:均值模糊.高斯模糊.锐化.Sobel.拉普拉斯.prewitt边缘检测等等一些和领域相关的算法,都可以通过卷积算法实现.只不过由于这些算法的卷 ...

  5. S5PV210_串行通信

    1.universal asynchronous reciver and transmitter 通用异步收发器 2.transmitter:由发送缓冲区和发送移位器构成.发送信息时,首先将信息编码( ...

  6. 国内优秀的Android资源

    因为一些大家都知道的原因,Android很多官方出品的优秀开发资源在国内无法访问. 国内的同行们对此也做出了很多努力,有很多朋友通过各种手段把很多优秀的资源搬运到了国内,为国内android开发者提供 ...

  7. [LeetCode] Maximal Rectangle 最大矩形

    Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing all ones and ...

  8. 一个c#的输入框函数

    private static string InputBox(string Caption, string Hint, string Default) { Form InputForm = new F ...

  9. PHP SPL(PHP 标准库)

    一.什么是SPL? SPL是用于解决典型问题(standard problems)的一组接口与类的集合.(出自:http://php.net/manual/zh/intro.spl.php) SPL, ...

  10. 【BZOJ 4580】【Usaco2016 Open】248

    http://www.lydsy.com/JudgeOnline/problem.php?id=4580 区间dp,f(i,j)表示区间[i,j]全部合成一个数,这个数是多少. 可以归纳证明[i,j] ...