博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 高级UI设计笔记06:仿微信图片选择器(转载)
阅读量:5251 次
发布时间:2019-06-14

本文共 35948 字,大约阅读时间需要 119 分钟。

仿微信图片选择器:

一、项目整体分析:

1. Android加载图片的3个目标:

(1)尽可能的去避免内存溢出。

  a. 根据图片的显示大小压缩图片

  b. 使用缓存对我们图片进行管理(LruCache)

(2)用户操作UI控件必须充分的流畅。

  a. getView里面尽可能不去做耗时的操作(异步加载 + 回调显示)

(3)用户预期显示的图片尽可能的快(图片的加载策略的选择,一般选择是LIFO)。

  a. LIFO

 

2. 定义一个Imageloader完成上面1中的3个目标:

Imageloader

getView()

{

    url   -> Bitmap

    url   -> LruCache 查找

                           ->找到返回

         ->找不到 url -> Task -> TaskQueue且发送一个通知去提醒后台轮询线程。

 

}

 •Task ->run() {根据url加载图片:

                  1. 获得图片显示的大小

                  2. 使用Options对图片进行压缩

                  3. 加载图片且放入LruCache

           }

   后台轮询线程

    TaskQueue ->Task ->将Task交给线程池去执行(执行run方法)

      一般情况下:(我们没有采用,效率低)

     new  Thread() {

                    run() {

                             while(true) {}

                     }

      }.start();

     这里这种场景,采用Handler + looper + Message

    

3. 项目最终的效果:

(1)默认显示图片最多的文件夹图片,以及底部显示图片总数量。如下图:

 

 

(2)点击底部,弹出popupWindow,popupWindow包含所有含有图片的文件夹,以及显示每个文件夹中图片数量。如下图:

      (注:此时Activity变暗

 

 

(3)选择任何文件夹,进入该文件夹图片显示,可以点击选择图片,当然了,点击已选择的图片则会取消选择。如下图:

    (注:选中图片变暗

二、代码实践 - 图片缓存、获取、展示

1.  打开Eclipse,新建一个Android工程,命名为"Imageloader",如下:

 

 

2. 新建一个包"com.himi.imageloader.util",编写一个图片加载工具类,如下:

 

ImageLoader.java,如下:

1 package com.himi.imageloader.util;  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.annotation.SuppressLint; 10 import android.graphics.Bitmap; 11 import android.graphics.BitmapFactory; 12 import android.graphics.BitmapFactory.Options; 13 import android.os.Handler; 14 import android.os.Looper; 15 import android.os.Message; 16 import android.util.DisplayMetrics; 17 import android.util.LruCache; 18 import android.view.ViewGroup.LayoutParams; 19 import android.widget.ImageView; 20  21 /** 22  * 图片加载类 23  * 这个类使用单例模式 24  * @author hebao 25  * 26  */ 27 public class ImageLoader { 28     private static ImageLoader mInstance; 29     /** 30      * 图片缓存的核心对象  31      *      管理我们所有图片加载的所需的内存 32      */ 33     private LruCache
mLruCache; 34 /** 35 * 线程池 36 * 执行一些我们加载图片的任务 37 */ 38 private ExecutorService mThreadPool; 39 /** 40 * 线程池中默认线程数 41 */ 42 private static final int DEAFULT_THREAD_COUNT = 1; 43 44 /** 45 * 队列的调度方式 46 */ 47 private Type mType = Type.LIFO; 48 /** 49 * 任务队列 50 * 任务队列提供给线程池取任务的 51 */ 52 private LinkedList
mTaskQueue; 53 /** 54 * 后台轮询线程 55 */ 56 private Thread mPoolThread; 57 /** 58 * 后台轮询线程的handler 59 */ 60 private Handler mPoolThreadHandler; 61 /** 62 * UI线程的handler 63 * 用于:更新ImageView 64 */ 65 private Handler mUIHandler; 66 /** 67 * mPoolThreadHandler的信号量,防止使用mPoolThreadHandler的时候其本身没有初始化完毕,报空指针异常 68 */ 69 private Semaphore mSemaphorePoolThreadHandler = new Semaphore(0); 70 /** 71 * 任务线程信号量,保证线程池真正做到LIFO 72 */ 73 private Semaphore mSemaphoreThreadPool; 74 75 /** 76 * 77 * 调度方式 78 *FIFO:先入先出 79 *LIFO:后入先出 80 */ 81 82 public enum Type { 83 FIFO,LIFO; 84 } 85 86 87 private ImageLoader(int threadCount, Type type) { 88 init(threadCount, type); 89 } 90 91 /** 92 * 初始化操作 93 * @param threadCount 94 * @param type 95 */ 96 private void init(int threadCount, Type type) { 97 //后台轮询线程初始化 98 mPoolThread = new Thread() { 99 @Override100 public void run() {101 Looper.prepare();102 mPoolThreadHandler = new Handler() {103 @Override104 public void handleMessage(Message msg) {105 //线程池取出一个任务进行执行106 mThreadPool.execute(getTask());107 try {108 mSemaphoreThreadPool.acquire();109 } catch (InterruptedException e) {110 // TODO 自动生成的 catch 块111 e.printStackTrace();112 }113 }114 };115 //释放一个信号量116 mSemaphorePoolThreadHandler.release();117 //Looper不断进行轮询118 Looper.loop();119 };120 };121 mPoolThread.start();122 123 //获取我们应用的最大可用内存124 int maxMemory = (int) Runtime.getRuntime().maxMemory();125 int cacheMemory = maxMemory / 8;126 //图片缓存初始化127 mLruCache = new LruCache
(cacheMemory) {128 /**129 * 测量每一个Bitmap图片的大小130 */131 @Override132 protected int sizeOf(String key, Bitmap value) {133 // 每一个Bitmap图片的大小 = 每一行字节数 * 高度134 return value.getRowBytes() * value.getHeight();135 }136 };137 138 //创建线程池139 mThreadPool = Executors.newFixedThreadPool(threadCount);140 mTaskQueue = new LinkedList
();141 mType = type;142 143 //初始化信号量144 mSemaphoreThreadPool = new Semaphore(threadCount);145 }146 147 /**148 * 从任务队列中取出一个方法 149 * @return150 */151 private Runnable getTask() {152 if(mType == Type.FIFO) {153 return mTaskQueue.removeFirst();154 }else if(mType == Type.LIFO) {155 return mTaskQueue.removeLast();156 }157 return null;158 }159 160 161 public static ImageLoader getInstance() {162 if(mInstance == null) {163 synchronized (ImageLoader.class) {164 if(mInstance == null) {165 mInstance = new ImageLoader(DEAFULT_THREAD_COUNT, Type.LIFO);166 }167 }168 169 }170 return mInstance;171 }172 173 public static ImageLoader getInstance(int threadCount, Type type) {174 if(mInstance == null) {175 synchronized (ImageLoader.class) {176 if(mInstance == null) {177 mInstance = new ImageLoader(threadCount, type);178 }179 }180 181 }182 return mInstance;183 }184 185 186 /**187 * 根据path为ImageView是设置图片188 * @param path189 * @param imageView190 */191 public void loadImage(final String path, final ImageView imageView ) {192 imageView.setTag(path);//设置Tag主要是为了校验,防止图片的混乱193 if(mUIHandler == null) {194 mUIHandler = new Handler() {195 @Override196 public void handleMessage(Message msg) {197 //获取得到图片,为imageview回调设置图片198 ImgBeanHolder holder = (ImgBeanHolder) msg.obj;199 Bitmap bm = holder.bitmap;200 ImageView imageview = holder.imageView;201 String path = holder.path;202 /**203 * 将path和getTag存储路径进行比较204 * 如果不比较,就会出现我们滑动到第二张图片,但是显示的还是第一张的图片205 * 这里我们绑定imageview和path就是为了防止这种情况206 */207 if(imageview.getTag().toString().equals(path)) {208 imageview.setImageBitmap(bm);209 }210 211 };212 };213 }214 //根据path在缓存中获取bitmap215 Bitmap bm = getBitmapFromLruCache(path);216 if(bm != null) {217 refreashBitmap(path, imageView, bm); 218 } else {
//内存中没有图片,加载图片到内存219 addTasks(new Runnable() {220 public void run() {221 /**加载图片222 * 图片的压缩223 */224 //1. 获得图片需要显示的大小225 ImageSize imageSize = getImageViewSize(imageView);226 //2. 压缩图片227 Bitmap bm = decodeSampleBitmapFromPath(path,imageSize.width,imageSize.height);228 //3. 把图片加载到缓存 (一定要记得)229 addBitmapToLruCache(path,bm); 230 refreashBitmap(path, imageView, bm); 231 //每次线程任务加载完图片,之后释放一个信号量,即:信号量-1,此时就会寻找下一个任务(根据FIFO/LIFO不同的策略取出任务)232 mSemaphoreThreadPool.release();233 }234 235 });236 }237 }238 239 240 public void refreashBitmap(final String path,241 final ImageView imageView, Bitmap bm) {242 Message message = Message.obtain(); 243 ImgBeanHolder holder = new ImgBeanHolder();244 holder.bitmap = bm;245 holder.path = path;246 holder.imageView = imageView;247 248 message.obj = holder;249 mUIHandler.sendMessage(message);250 } 251 252 /**253 * 将图片加入缓存LruCache254 * @param path255 * @param bm256 */257 private void addBitmapToLruCache(String path, Bitmap bm) {258 if(getBitmapFromLruCache(path) == null) {259 if(bm != null) {260 mLruCache.put(path, bm);261 }262 }263 264 }265 266 267 /**268 * 根据图片需要显示的宽和高,对图片进行压缩269 * @param path270 * @param width271 * @param height272 * @return273 */274 private Bitmap decodeSampleBitmapFromPath(String path,275 int width, int height) {276 //获取图片的宽和高,但是不把图片加载到内存中277 BitmapFactory.Options options = new BitmapFactory.Options();278 options.inJustDecodeBounds =true;//不把图片加载到内存中279 BitmapFactory.decodeFile(path, options);280 281 options.inSampleSize = caculateInSampleSize(options,width, height);//计算获取压缩比282 //使用获取到的inSampleSize再次解析图片283 options.inJustDecodeBounds =false;//加载图片到内存284 Bitmap bitmap = BitmapFactory.decodeFile(path, options);285 286 287 return bitmap;288 }289 290 291 /**292 *根据需求的宽和高,以及图片实际的宽和高,计算inSampleSize293 * @param options294 * @param width295 * @param height296 * @return inSampleSize 压缩比297 */298 private int caculateInSampleSize(Options options, int reqWidth, int reqHeight) {299 int width = options.outWidth;300 int height = options.outHeight;301 302 int inSampleSize = 1;303 if(width>reqWidth || height > reqHeight) {304 int widthRadio = Math.round(width*1.0f / reqWidth);305 int heightRadio = Math.round(height*1.0f / reqHeight);306 307 inSampleSize = Math.max(widthRadio, heightRadio); 308 }309 310 return inSampleSize;311 }312 313 /**314 * 根据ImageView获取适当的压缩的宽和高315 * @param imageView316 * @return317 */318 protected ImageSize getImageViewSize(ImageView imageView) {319 ImageSize imageSize = new ImageSize();320 DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();321 LayoutParams lp = imageView.getLayoutParams();322 323 int width = imageView.getWidth();//获取imageview的实际宽度324 if(width<=0) {325 width = lp.width;//获取imageview在layout中声明的宽度326 }327 if(width<=0) {328 width = getImageViewFieldValue(imageView, "mMaxWidth");//利用反射,检测获得最大值329 }330 if(width<=0) {331 width = displayMetrics.widthPixels;332 }333 334 335 int height = imageView.getHeight();//获取imageview的实际高度336 if(height<=0) {337 height = lp.height;//获取imageview在layout中声明的高度338 }339 if(height<=0) {340 height = getImageViewFieldValue(imageView, "mMaxHeight");//利用反射,检测获得最大值341 }342 if(height<=0) {343 height = displayMetrics.heightPixels;344 }345 346 imageSize.width = width;347 imageSize.height = height;348 return imageSize;349 };350 351 /**352 * 353 * 通过反射获取imageview的某个属性值354 * @param object355 * @param fieldName356 * @return357 * 由于方法getMaxHeight是API16以上的才能使用,这里我们用反射使用这个方法358 */359 private static int getImageViewFieldValue(Object object, String fieldName) {360 int value=0;361 try {362 Field field = ImageView.class.getDeclaredField(fieldName);363 field.setAccessible(true);364 365 int fieldValue = field.getInt(object);366 if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {367 value = fieldValue;368 }369 } catch (Exception e) {370 // TODO 自动生成的 catch 块371 e.printStackTrace();372 }373 return value;374 }375 376 /**377 * 添加任务到任务队列,交给线程池执行378 * @param runnable379 */380 @SuppressLint("NewApi")381 private synchronized void addTasks(Runnable runnable) {
//synchronized同步代码,防止多个线程进来出现死锁382 mTaskQueue.add(runnable);383 //if(mPoolThreadHandler == null) wait();384 //确保我们在使用mPoolThreadHandler之前,我们初始化完毕mPoolThreadHandler(不为空),这里引入信号量385 try {386 if(mPoolThreadHandler == null) {387 mSemaphorePoolThreadHandler.acquire();388 } 389 } catch (InterruptedException e) {390 // TODO 自动生成的 catch 块391 e.printStackTrace();392 }393 mPoolThreadHandler.sendEmptyMessage(0x110);394 395 396 }397 398 399 /**400 * 根据path在缓存中获取bitmap401 * @param key402 * @return403 */404 private Bitmap getBitmapFromLruCache(String key) {405 // TODO 自动生成的方法存根406 return mLruCache.get(key);407 }408 409 /**410 * 压缩图片之后的宽和高411 * @author Administrator412 *413 */414 private class ImageSize {415 int width;416 int height;417 }418 419 private class ImgBeanHolder {420 Bitmap bitmap;421 ImageView imageView;422 String path;423 }424 425 }

 

三、代码实践 - UI、UI适配器

1. 布局文件设计,首先我们从美工那边获得布局设计需要的图片,如下:

 

来到activity_main.xml,如下:

1 
6 7
13 14
24
32
42
52 53
54 55 56

显示布局效果如下:

 

来到item_gridview.xml,如下:

1 
6 7
8 9
15 16
28 29

布局效果如下:

 

2. 这里我们首先对手机中图片进行扫描,拿到图片数量最多的,直接显示在GridView上;并且扫描结束,得到一个所有包含图片的文件夹信息的集合。为了便于存储手机中所有文件夹信息,我们单独创建一个Bean实体类,命名为"FolderBean",新建包com.himi.imageloader.bean,将这个类放在里面,如下:

1 package com.himi.imageloader.bean; 2  3 /** 4  * FolderBean :图片的文件夹信息类 5  *  6  * 注意: 7  *    用来存储当前文件夹的路径,当前文件夹包含多少张图片,以及第一张图片路径用于做文件夹的图标; 8  *    注:文件夹的名称,我们在set文件夹的路径的时候,自动提取,仔细看下setDir这个方法. 9  * 10  * @author hebao11  * 12  */13 14 public class FolderBean {15     /**16      * 图片的文件夹路径17      */18     private String dir;19 20     /**21      * 第一张图片的路径22      */23     private String firstImgPath;24 25     /**26      * 文件夹的名称27      */28     private String name;29 30     /**31      * 图片的数量32      */33     private int count;34 35     public String getDir() {36         return dir;37     }38 39     public void setDir(String dir) {40         this.dir = dir;41         int lastIndexOf = this.dir.lastIndexOf("/");42         this.name = this.dir.substring(lastIndexOf);43     }44 45     public String getFirstImgPath() {46         return firstImgPath;47     }48 49     public void setFirstImgPath(String firstImgPath) {50         this.firstImgPath = firstImgPath;51     }52 53     public String getName() {54         return name;55     }56 57     public int getCount() {58         return count;59     }60 61     public void setCount(int count) {62         this.count = count;63     }64 65 }

 

 3. 接下来自然要说到扫描手机图片的代码,在MainActivity中,如下:

1     @Override  2     protected void onCreate(Bundle savedInstanceState) {  3         super.onCreate(savedInstanceState);  4         setContentView(R.layout.activity_main);  5         initView();  6         initDatas();  7         initEvent();  8     }  9  10     private void initView() { 11         mGridView = (GridView) findViewById(R.id.id_gridView); 12         mBottomLy = (RelativeLayout) findViewById(R.id.id_bottom_ly); 13         mDirName = (TextView) findViewById(R.id.id_dir_name); 14         mDirCount = (TextView) findViewById(R.id.id_dir_count); 15  16     } 17  18     /** 19      * 利用ContentProvider扫描手机中的图片,此方法在运行在子线程中 完成图片的扫描,最终获得jpg最多的那个文件夹  20      */ 21     private void initDatas() { 22  23         if (!Environment.getExternalStorageState().equals( 24                 Environment.MEDIA_MOUNTED)) { 25             Toast.makeText(this, "当前存储卡不可用", Toast.LENGTH_SHORT).show(); 26             return; 27         } 28         /** 29          * 显示进度条 30          */ 31         mProgressDialog = ProgressDialog.show(this, null, "正在加载……"); 32         /** 33          * 扫描手机中所有的图片,很明显这是一个耗时的操作,所以我们不能在UI线程中,采用子线程. 34          * 扫描得到的文件夹及其图片信息 在 List
mFolderBeans存储. 35 */ 36 new Thread() { 37 public void run() { 38 Uri mImgUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 39 ContentResolver cr = MainActivity.this.getContentResolver(); 40 //只查询jpeg和png的图片 41 Cursor cursor = cr.query(mImgUri, null, 42 MediaStore.Images.Media.MIME_TYPE + "? or" 43 + MediaStore.Images.Media.MIME_TYPE + "?", 44 new String[] { "image/jpeg", "image/png", }, 45 MediaStore.Images.Media.DATE_MODIFIED); 46 47 /** 48 * 存放已经遍历的文件夹路径,防止重复遍历 49 */ 50 Set
mDirPaths = new HashSet
(); 51 /** 52 * 遍历手机图片 53 */ 54 while (cursor.moveToNext()) { 55 // 获取图片的路径 56 String path = cursor.getString(cursor 57 .getColumnIndex(MediaStore.Images.Media.DATA)); 58 // 获取该图片的父路径名 59 File parentFile = new File(path).getParentFile(); 60 if (parentFile == null) { 61 continue; 62 } 63 String dirPath = parentFile.getAbsolutePath(); 64 65 FolderBean folderBean = null; 66 // 利用一个HashSet防止多次扫描同一个文件夹(不加这个判断,图片多起来还是相当恐怖的~~) 67 if (mDirPaths.contains(dirPath)) { 68 continue; 69 } else { 70 mDirPaths.add(dirPath); 71 // 初始化imageFloder 72 folderBean = new FolderBean(); 73 74 //图片的文件夹路径 75 folderBean.setDir(dirPath); 76 //第一张图片的路径 77 folderBean.setFirstImgPath(path); 78 } 79 //有些图片比较诡异~~;无法显示,这里加判断,防止空指针异常 80 if (parentFile.list() == null) { 81 continue; 82 } 83 84 int picSize = parentFile.list(new FilenameFilter() { 85 86 public boolean accept(File dir, String filename) { 87 if (filename.endsWith(".jpg") 88 || filename.endsWith(".jpeg") 89 || filename.endsWith(".png")) { 90 return true; 91 } 92 return false; 93 } 94 }).length; 95 //图片的数量 96 folderBean.setCount(picSize); 97 mFolderBeans.add(folderBean); 98 /** 99 * 如果此时扫描到图片文件夹中图片数量最多,则赋值给mMaxCount,mCurrentDir100 */101 if (picSize > mMaxCount) {102 mMaxCount = picSize;103 mCurrentDir = parentFile;104 }105 106 }107 //关闭游标108 cursor.close();109 // 通知handler扫描图片完成110 mHandler.sendEmptyMessage(DATA_LOADED);111 112 };113 }.start();114 115 }

initView就不看了,都是些findViewById;

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

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

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

2)有了mFolderBeans,就可以创建我们的popupWindow了;

1 private Handler mHandler = new Handler() { 2  3         public void handleMessage(android.os.Message msg) { 4             if (msg.what == DATA_LOADED) { 5                 mProgressDialog.dismiss(); 6                 // 绑定数据到GridView 7                 data2View(); 8                 // 初始化PopupWindow 9                 initDirPopupWindow();10             }11         }12     };

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

1)在MainActivity中,data2View如下:

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

1 /** 2      * 为View绑定数据  3      */ 4     private void data2View() { 5         if (mCurrentDir == null) { 6             Toast.makeText(this, "未扫描到任何图片", Toast.LENGTH_SHORT).show(); 7             return; 8         } 9 10         mImgs = Arrays.asList(mCurrentDir.list());11         12         /** 13          * 可以看到文件夹的路径和图片的路径分开保存,极大的减少了内存的消耗; 14          */  15         mImgAdapter = new ImageAdapter(this, mImgs,16                 mCurrentDir.getAbsolutePath());17         mGridView.setAdapter(mImgAdapter);18 19         mDirCount.setText(mMaxCount + "");20         mDirName.setText(mCurrentDir.getName());21 22     };

2)看到上面(1)还用到了一个Adapter(for GridView),我们自定义一个适配器ImageAdapter继承自BaseAdapter它和MainActivity所处一个包下,如下

1 package com.himi.imageloader;  2   3 import java.util.HashSet;  4 import java.util.List;  5 import java.util.Set;  6   7 import android.content.Context;  8 import android.graphics.Color;  9 import android.view.LayoutInflater; 10 import android.view.View; 11 import android.view.View.OnClickListener; 12 import android.view.ViewGroup; 13 import android.widget.BaseAdapter; 14 import android.widget.ImageButton; 15 import android.widget.ImageView; 16  17 import com.himi.imageloader.util.ImageLoader; 18 import com.himi.imageloader.util.ImageLoader.Type; 19  20 public class ImageAdapter extends BaseAdapter { 21         /**  22          * 用户选择的图片,存储为图片的完整路径  23          */   24         private static Set
mSelectedImg = new HashSet
(); 25 /** 26 * 文件夹路径 27 */ 28 private String mDirPath; 29 private List
mImgPaths; 30 private LayoutInflater mInflater; 31 //分开存储文件目录,和文件名。节省内存 32 public ImageAdapter(Context context, List
mDatas, String dirPath) { 33 this.mDirPath = dirPath; 34 this.mImgPaths = mDatas; 35 mInflater = LayoutInflater.from(context); 36 } 37 38 public int getCount() { 39 return mImgPaths.size(); 40 } 41 42 public Object getItem(int position) { 43 return mImgPaths.get(position); 44 } 45 46 public long getItemId(int position) { 47 return position; 48 } 49 50 public View getView(final int position, View convertView, ViewGroup parent) { 51 final ViewHolder viewHolder; 52 if(convertView == null) { 53 convertView = mInflater.inflate(R.layout.item_gridview, parent,false); 54 55 viewHolder = new ViewHolder(); 56 viewHolder.mImg = (ImageView) convertView.findViewById(R.id.id_item_image); 57 viewHolder.mSelect = (ImageButton) convertView.findViewById(R.id.id_item_select); 58 convertView.setTag(viewHolder); 59 } else { 60 viewHolder = (ViewHolder) convertView.getTag(); 61 } 62 63 /** 64 * 重置状态,如果不重置第一次选中,第二次还会复用之前的,这样就会产生错乱 65 */ 66 viewHolder.mImg.setImageResource(R.drawable.pictures_no); 67 viewHolder.mSelect.setImageResource(R.drawable.picture_unselected); 68 viewHolder.mImg.setColorFilter(null); 69 70 ImageLoader.getInstance(3, Type.LIFO).loadImage(mDirPath+"/"+mImgPaths.get(position), 71 viewHolder.mImg); 72 final String filePath = mDirPath+"/"+mImgPaths.get(position); 73 74 // 设置ImageView的点击事件 75 viewHolder.mImg.setOnClickListener(new OnClickListener() { 76 // 选择,则将图片变暗,反之则反之 77 public void onClick(View v) { 78 //已经被选择 79 if(mSelectedImg.contains(filePath)) { 80 mSelectedImg.remove(filePath); 81 //改变Item状态,没有必要刷新显示 82 viewHolder.mImg.setColorFilter(null); 83 viewHolder.mSelect.setImageResource(R.drawable.picture_unselected); 84 }else {
//未被选择 85 mSelectedImg.add(filePath); 86 //改变Item状态,没有必要刷新显示 87 viewHolder.mImg.setColorFilter(Color.parseColor("#77000000")); 88 viewHolder.mSelect.setImageResource(R.drawable.pictures_selected); 89 } 90 //notifyDataSetChanged();不能使用,会出现闪屏 91 92 } 93 }); 94 95 /** 96 * 已经选择过的图片,显示出选择过的效果 97 */ 98 if(mSelectedImg.contains(filePath)) { 99 viewHolder.mImg.setColorFilter(Color.parseColor("#77000000"));100 viewHolder.mSelect.setImageResource(R.drawable.pictures_selected);101 }102 103 return convertView;104 }105 106 private class ViewHolder {107 ImageView mImg;108 ImageButton mSelect;109 }110 111 }

图片策略我们使用的是LIFO后进先出。

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

 

四、展现文件夹的PopupWindow

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

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

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

1.  自定义PopupWindow,命名为"ListImageDirPopupWindow ",如下:

1 package com.himi.imageloader;  2   3 import java.util.List;  4   5 import android.content.Context;  6 import android.graphics.drawable.BitmapDrawable;  7 import android.util.DisplayMetrics;  8 import android.view.LayoutInflater;  9 import android.view.MotionEvent; 10 import android.view.View; 11 import android.view.View.OnTouchListener; 12 import android.view.ViewGroup; 13 import android.view.WindowManager; 14 import android.widget.AdapterView; 15 import android.widget.AdapterView.OnItemClickListener; 16 import android.widget.ArrayAdapter; 17 import android.widget.ImageView; 18 import android.widget.ListView; 19 import android.widget.PopupWindow; 20 import android.widget.TextView; 21  22 import com.himi.imageloader.bean.FolderBean; 23 import com.himi.imageloader.util.ImageLoader; 24  25 /** 26  * 自定义的PopupWindow 27  * 作用:展现文件夹信息 28  * @author hebao 29  * 30  */ 31 public class ListImageDirPopupWindow extends PopupWindow { 32     private int mWidth; 33     private int mHeight; 34     private View mConvertView; 35     private ListView mListView; 36      37      38     private List
mDatas; 39 40 41 /** 42 * 文件夹选中的监听器(接口) 43 * @author hebao 44 * 45 */ 46 public interface OnDirSelectedListener { 47 void onSelected(FolderBean folderBean); 48 } 49 public OnDirSelectedListener mListener; 50 public void setOnDirSelectedListener (OnDirSelectedListener mListener) { 51 this.mListener = mListener; 52 } 53 54 55 56 public ListImageDirPopupWindow(Context context, List
datas) { 57 calWidthAndHeight(context); 58 59 mConvertView = LayoutInflater.from(context).inflate(R.layout.popup_main, null); 60 setContentView(mConvertView); 61 62 setWidth(mWidth); 63 setHeight(mHeight); 64 65 //设置可触摸 66 setFocusable(true); 67 setTouchable(true); 68 setOutsideTouchable(true); 69 setBackgroundDrawable(new BitmapDrawable()); 70 71 setTouchInterceptor(new OnTouchListener() { 72 73 public boolean onTouch(View v, MotionEvent event) { 74 if(event.getAction() == MotionEvent.ACTION_OUTSIDE){ 75 dismiss(); 76 return true; 77 } 78 return false; 79 } 80 }); 81 82 initViews(context); 83 initEvent(); 84 85 } 86 87 private void initViews(Context context) { 88 mListView = (ListView) mConvertView.findViewById(R.id.id_list_dir); 89 mListView.setAdapter(new ListDirAdapter(context, mDatas)); 90 } 91 92 /** 93 * 设置监听事件 94 */ 95 private void initEvent() { 96 mListView.setOnItemClickListener(new OnItemClickListener() { 97 98 public void onItemClick(AdapterView
parent, View view, 99 int position, long id) {100 if(mListener != null) {101 mListener.onSelected(mDatas.get(position));102 }103 104 }105 106 });107 108 }109 110 111 112 /**113 * 计算popupWindow的宽度和高度114 * @param context115 */116 private void calWidthAndHeight(Context context) {117 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);118 //Andorid.util 包下的DisplayMetrics 类提供了一种关于显示的通用信息,如显示大小,分辨率和字体。119 DisplayMetrics outMetrics = new DisplayMetrics();120 wm.getDefaultDisplay().getMetrics(outMetrics);121 122 123 mWidth = outMetrics.widthPixels;124 mHeight = (int) (outMetrics.heightPixels * 0.7);125 }126 127 128 private class ListDirAdapter extends ArrayAdapter
{129 private LayoutInflater mInflater;130 private List
mDatas;131 132 public ListDirAdapter(Context context,133 List
objects) {134 super(context, 0, objects);135 mInflater = LayoutInflater.from(context);136 }137 138 @Override139 public View getView(int position, View convertView, ViewGroup parent) {140 ViewHolder holder = null;141 if(convertView == null) {142 holder = new ViewHolder();143 convertView = mInflater.inflate(R.layout.item_popup_main, parent, false);144 145 holder.mImg = (ImageView) convertView.findViewById(R.id.id_id_dir_item_image);146 holder.mDirName = (TextView) convertView.findViewById(R.id.id_dir_item_name);147 holder.mDirCount = (TextView) convertView.findViewById(R.id.id_dir_item_count);148 149 convertView.setTag(holder);150 } else {151 holder =(ViewHolder) convertView.getTag();152 }153 FolderBean bean =getItem(position);154 //重置155 holder.mImg.setImageResource(R.drawable.pictures_no);156 157 //回调加载图片158 ImageLoader.getInstance().loadImage(bean.getFirstImgPath(), holder.mImg); 159 holder.mDirCount.setText(bean.getCount()+"");160 holder.mDirName.setText(bean.getName());161 return convertView;162 }163 164 private class ViewHolder {165 ImageView mImg;166 TextView mDirName;167 TextView mDirCount;168 }169 }170 171 }

好了,现在就是我们正在的popupWindow咯,布局文件夹主要是个ListView,所以在initViews里面,我们得设置它的适配器;当然了,这里的适配器依然用我们的ListDirAdapter。

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

关于交互,我们从Activity的角度去看弹出框,Activity想知道什么,只想知道选择了别的文件夹来告诉我,所以我们创建一个接口OnDirSelectedListener ,对Activity设置回调;initEvent初始化事件,如果有人设置了回调,我们就调用。

 

2.  接下来到MainActivity,完成MainActivity和PopupWindow的交互,如下:

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

在handleMessage里面调用 initDirPopupWindow

1 /** 2      * 初始化展示文件夹的popupWindw  3      */ 4     private void initDirPopupWindow() { 5         mDirPopupWindow = new ListImageDirPopupWindow(this, mFolderBeans); 6  7         mDirPopupWindow.setOnDismissListener(new OnDismissListener() { 8  9             public void onDismiss() {10                 lightOn();11 12             }13         });14 15         /**16          *  设置选择文件夹的回调  17          */18         mDirPopupWindow.setOnDirSelectedListener(new OnDirSelectedListener() {19 20             public void onSelected(FolderBean folderBean) {21                 mCurrentDir = new File(folderBean.getDir());22                 mImgs = Arrays.asList(mCurrentDir.list(new FilenameFilter() {23 24                     public boolean accept(File dir, String filename) {25                         if (filename.endsWith(".jpg")26                                 || filename.endsWith(".jpeg")27                                 || filename.endsWith(".png")) {28                             return true;29                         }30                         return false;31                     }32                 }));33 34                 mImgAdapter = new ImageAdapter(MainActivity.this, mImgs,35                         mCurrentDir.getAbsolutePath());36                 mGridView.setAdapter(mImgAdapter);37 38                 mDirCount.setText(mImgs.size() + "");39                 mDirName.setText(folderBean.getName());40 41                 mDirPopupWindow.dismiss();42             }43         });44 45     }46 47     /**48      * 内容区域变亮49      */50 51     protected void lightOn() {52         WindowManager.LayoutParams lp = getWindow().getAttributes();53         lp.alpha = 1.0f;54         getWindow().setAttributes(lp);55     }56 57     /**58      * 内容区域变暗59      */60     protected void lightOff() {61         WindowManager.LayoutParams lp = getWindow().getAttributes();62         lp.alpha = .3f;63         getWindow().setAttributes(lp);64 65     }

我们初始化我们的popupWindow,设置了关闭对话框的回调,已经设置了选择不同文件夹的回调;

这里仅仅是初始化,下面看我们合适将其弹出的,其实整个Activity也就一个事件,点击弹出该对话框,所以看Activity的initEvent方法:

1 /** 2      * 添加点击事件 3      */ 4     private void initEvent() { 5         mBottomLy.setOnClickListener(new OnClickListener() { 6  7             public void onClick(View v) { 8                 // 设置PopupWindow动画 9                 mDirPopupWindow.setAnimationStyle(R.style.dir_popupwindow_anim);10 11                 // 设置PopupWindow的出现12                 mDirPopupWindow.showAsDropDown(mBottomLy, 0, 0);13                 lightOff();14 15             }16         });17 18     }

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

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

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

 

 

五、总结:

1. Imageloader:

(1)Handler + Loop + Message(new Thread().start():这种方式效率低

(2) 图片的压缩

    获取图片应当显示的尺寸---> 使用options进行压缩

(3) 图片显示避免错乱

           setTag(url);

 

2. PopupWindow:

单独自定义一个PopupWindow继承自系统的PopupWindow。

然后处理自己的子View事件,把一些关键的回调接口和方法进行返回,让MainActivity进行设置

 

3. 注意:

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

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

 

源码下载:

转载于:https://www.cnblogs.com/hebao0514/p/4910190.html

你可能感兴趣的文章
类加载机制
查看>>
tju 1782. The jackpot
查看>>
HTML5与CSS3基础(五)
查看>>
WinDbg调试C#技巧,解决CPU过高、死锁、内存爆满
查看>>
linux脚本中有source相关命令时的注意事项
查看>>
css样式表中的样式覆盖顺序
查看>>
湖南多校对抗赛(2015.03.28) H SG Value
查看>>
REST Web 服务(二)----JAX-RS 介绍
查看>>
hdu1255扫描线计算覆盖两次面积
查看>>
hdu1565 用搜索代替枚举找可能状态或者轮廓线解(较优),参考poj2411
查看>>
bzoj3224 splay板子
查看>>
程序存储问题
查看>>
Mac版OBS设置详解
查看>>
优雅地书写回调——Promise
查看>>
android主流开源库
查看>>
AX 2009 Grid控件下多选行
查看>>
PHP的配置
查看>>
Struts框架----进度1
查看>>
Round B APAC Test 2017
查看>>
MySQL 字符编码问题详细解释
查看>>