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

   

一、目标

1、尽可能避免内存溢出
A、根据图片的显示大小去压缩图片
B、使用缓存对图片进行管理(LruCache)
2、保证用户操作UI尽可能的流畅
在getView()方法中尽量不做耗时操作(异步显示 + 回调显示)
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中的代码:

 <?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="@color/cl_white"> <RelativeLayout
android:layout_width="match_parent"
android:layout_height="50.0dip"
android:background="#ee000000"> <ImageView
android:id="@+id/position_main_iv_icon"
android:layout_width="35.0dip"
android:layout_height="35.0dip"
android:layout_centerVertical="true"
android:layout_marginLeft="10.0dip"
android:src="@mipmap/ic_launcher" /> <TextView
style="@style/Style_Main_TextView"
android:layout_marginLeft="10.0dip"
android:layout_toRightOf="@+id/position_main_iv_icon"
android:text="图片选择器" />
</RelativeLayout> <RelativeLayout
android:id="@+id/find_main_rl_bottomlayout"
android:layout_width="match_parent"
android:layout_height="50.0dip"
android:layout_alignParentBottom="true"
android:background="#ee000000"> <TextView
android:id="@+id/find_main_tv_toall"
style="@style/Style_Main_TextView"
android:layout_marginLeft="10.0dip"
android:text="所有图片" /> <TextView
android:id="@+id/find_main_tv_num"
style="@style/Style_Main_TextView"
android:layout_alignParentRight="true"
android:layout_marginRight="10.0dip"
android:text="共100张" />
</RelativeLayout> <GridView
android:id="@+id/find_main_gv_images"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginBottom="50.0dip"
android:layout_marginTop="50.0dip"
android:cacheColorHint="@android:color/transparent"
android:horizontalSpacing="3.0dip"
android:listSelector="@android:color/transparent"
android:numColumns="3"
android:stretchMode="columnWidth"
android:verticalSpacing="3.0dip" /> </RelativeLayout>

MainActivity.java中的代码:

 package com.itgungnir.activity;

 import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.view.View;
import android.view.WindowManager;
import android.widget.GridView;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast; import com.itgungnir.entity.FolderModel;
import com.itgungnir.tools.ImageAdapter;
import com.itgungnir.view.DirsPopWindow; import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set; public class MainActivity extends Activity {
private GridView imageGrid;
private TextView dirName;
private TextView dirCount;
private ProgressDialog progressDialog; // 加载图片时出现的加载对话框
private DirsPopWindow popWindow; // 可弹出的目录菜单
private RelativeLayout bottomLayout; private List<String> imageList; // 图片的数据集
private File currentDir; // 当前所在的文件目录
private int totalCount; // 显示dirCount中的数据
private List<FolderModel> folderModels = new ArrayList<FolderModel>();
private ImageAdapter imageAdapter; private static final int DATA_LOADED = 0x110; private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == DATA_LOADED) {
progressDialog.dismiss();
bindDataToView(); initDirsPopWindow();
}
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
initEvent();
} /**
* 初始化弹出菜单
*/
private void initDirsPopWindow() {
popWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
lightOn();
}
});
} /**
* 弹出窗口消失之后,需要将后面的图片列表变亮
*/
private void lightOn() {
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.alpha = 1.0f;
getWindow().setAttributes(lp);
} /**
* 在现实PopUpWindow之后将后面的图片列表置黑
*/
private void lightOff() {
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.alpha = 0.3f;
getWindow().setAttributes(lp);
} /**
* 绑定数据到View中
*/
private void bindDataToView() {
if (currentDir == null) {
Toast.makeText(MainActivity.this, "未扫描到任何图片!", Toast.LENGTH_SHORT).show();
return;
}
imageList = Arrays.asList(currentDir.list());
imageAdapter = new ImageAdapter(MainActivity.this, imageList, currentDir.getAbsolutePath());
imageGrid.setAdapter(imageAdapter); dirCount.setText(totalCount + "");
dirName.setText(currentDir.getName());
} /**
* 初始化数据(利用ContentProvider扫描手机中的所有图片)
*/
private void initData() {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
Toast.makeText(MainActivity.this, "当前存储卡不可用!", Toast.LENGTH_SHORT).show();
return;
}
progressDialog = ProgressDialog.show(MainActivity.this, null, "正在加载...");
new Thread() {
@Override
public void run() {
/**
* 初始化FolderModel,为PopUpWindow做准备
*/
Uri imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver cr = MainActivity.this.getContentResolver();
Cursor cursor = cr.query(imageUri, null, MediaStore.Images.Media.MIME_TYPE + " = ? or " + MediaStore.Images.Media.MIME_TYPE + " = ? ",
new String[]{"image/jpeg", "image/png"}, MediaStore.Images.Media.DATE_MODIFIED);
Set<String> dirPaths = new HashSet<String>();
while (cursor.moveToNext()) {
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); // 获取图片的路径
File parentFile = new File(path).getParentFile(); // 获取该图片所在的父路径名
if (parentFile == null) {
continue;
}
String dirPath = parentFile.getAbsolutePath();
FolderModel folder = null;
// 放置重复扫描
if (dirPaths.contains(dirPath)) {
continue;
} else {
dirPaths.add(dirPath);
folder = new FolderModel();
folder.setDir(dirPath);
folder.setFirstImgPath(path);
}
if (parentFile.list() == null) {
continue;
}
int picSize = parentFile.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
if (filename.endsWith(".jpg") || filename.endsWith(".jpeg") || filename.endsWith(".png")) {
return true;
}
return false;
}
}).length;
folder.setCount(picSize);
folderModels.add(folder);
if (picSize > totalCount) {
totalCount = picSize;
currentDir = parentFile;
}
}
cursor.close();
// 扫描完成,释放临时变量的内存
dirPaths = null;
handler.sendEmptyMessage(DATA_LOADED); // 通知Handler扫描图片完成
}
}.start();
} /**
* 初始化事件
*/
private void initEvent() {
bottomLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
popWindow.setAnimationStyle(R.style.Style_PopWindow_Anim);
popWindow.showAsDropDown(bottomLayout, 0, 0);
lightOff();
}
}); popWindow.setOnDirSelectListener(new DirsPopWindow.OnDirSelectListener() {
@Override
public void onDirSelected(FolderModel folder) {
currentDir = new File(folder.getDir());
imageList = Arrays.asList(currentDir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
if (filename.endsWith(".jpg") || filename.endsWith(".jpeg") || filename.endsWith(".png")) {
return true;
}
return false;
}
}));
imageAdapter = new ImageAdapter(MainActivity.this, imageList, currentDir.getAbsolutePath());
imageGrid.setAdapter(imageAdapter);
dirCount.setText(imageList.size() + "");
dirName.setText(folder.getName()); popWindow.dismiss();
}
});
} /**
* 初始化控件
*/
private void initView() {
imageGrid = (GridView) findViewById(R.id.find_main_gv_images);
dirName = (TextView) findViewById(R.id.find_main_tv_toall);
dirCount = (TextView) findViewById(R.id.find_main_tv_num);
bottomLayout = (RelativeLayout) findViewById(R.id.find_main_rl_bottomlayout);
popWindow = new DirsPopWindow(MainActivity.this, folderModels);
} @Override
protected void onDestroy() {
progressDialog.dismiss();
super.onDestroy();
}
}

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

 package com.itgungnir.tools;

 import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v4.util.LruCache;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView; /**
* ********图片加载类*********
* ***思路:图片的加载在Adapter的getView()方法中执行,我们根据一个图片的URL到LruCache缓存中寻找Bitmap图片,如果找到则返回图片,
* 如果找不到,则会根据URL产生一个Task并放到TaskQueue中,同时发送一个通知提醒后台轮询线程,轮询线程从TaskQueue中取出一个Task到线程池去执行(执行的是Task的run()方法,
* 具体为:获得图片显示的实际大小;使用Options对图片进行压缩;加载图片且放入LruCache)
* ***核心:Handler + Looper + Message(Android异步消息处理框架)
* 当我们用Handler发送消息时,会把信息放到MessageQueue中,轮询线程会取出这条消息,交给Handler的handleMessage()方法进行处理
*/
public class ImageLoader {
private static ImageLoader mInstance; // 实例
private LruCache<String, Bitmap> mLruCache; // 图片缓存的核心类
private ExecutorService mThreadPool; // 线程池
private static final int DEFAULT_THREAD_COUNT = 1; // 线程池的线程数量,默认为1
private Type mType = Type.LIFO; // 任务队列的默认调度方式
private LinkedList<Runnable> taskQueue; // 任务队列
private Thread mPoolThread; // 后台轮询线程
private Handler mPoolThreadHandler; // 与后台轮询线程绑定的Handler
private Handler uiHandler; // 运行在UI线程的handler,用于给ImageView设置图片
/**
* 引入一个值为1的信号量,防止mPoolThreadHander未初始化完成
* Semaphore的作用是限制某一资源的同步访问
* 可以把Semaphore理解成一个可以容纳N个人的房间,如果人数没有达到N就可以进去,如果人满了,就要等待有人出来才可以进去
* 在addTask()方法中需要用到后台轮询线程poolThreadHandler,但存在线程同步问题,
* 即addTask()方法可能在poolThreadHandler初始化之前就被调用了,所以我们需要定义这样一个“信号量”来调控线程同步
*/
private volatile Semaphore mPoolThreadHandlerSemaphore = new Semaphore(0); // 控制addTask()方法在mPoolThreadHandler吃实话之后才能调用
private volatile Semaphore mPoolSemaphore; // 引入一个值为1的信号量,由于线程池内部也有一个阻塞线程,防止加入任务的速度过快,使LIFO效果不明显 /**
* 图片加载的策略(FIFO先进先出、LIFO后进先出)
*/
public enum Type {
FIFO, LIFO
} /**
* 构造函数
* 由于ImageLoader中需要使用LruCache来缓存图片,需要占据较大的空间,所以整个项目中只需要一个ImageLoader即可(需要使用单例模式)
* 因此我们把构造方法设为private权限,防止用户new出实例
*
* @param threadCount 任务队列中默认线程数
* @param type 图片加载策略(先进先出/后进先出)
*/
private ImageLoader(int threadCount, Type type) {
init(threadCount, type);
} /**
* 单例获得该实例对象(无参数,按照默认值进行初始化)
*/
public static ImageLoader getInstance() {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(DEFAULT_THREAD_COUNT, Type.LIFO);
}
}
}
return mInstance;
} /**
* 单例获得该实例对象(有参数,用户可以根据实际需要对ImageLoader进行实例化)
*/
public static ImageLoader getInstance(int threadCount, Type type) {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(threadCount, type);
}
}
}
return mInstance;
} /**
* 完成成员变量的初始化操作
*/
private void init(int threadCount, Type type) {
/**
* 初始化后台轮询线程(使用Looper、Handler、Message实现)
*/
mPoolThread = new Thread() {
@Override
public void run() {
Looper.prepare();
mPoolThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mThreadPool.execute(getTask()); // 线程池取出一个任务去执行
try {
mPoolSemaphore.acquire();
} catch (InterruptedException e) {
}
}
};
mPoolThreadHandlerSemaphore.release(); // 释放一个信号量
Looper.loop(); // 开始无限循环
}
};
mPoolThread.start();
/**
* 初始化LruCache
*/
int maxMemory = (int) Runtime.getRuntime().maxMemory(); // 获取应用程序最大可用内存
int cacheSize = maxMemory / 8; // 设置缓存的存储空间是总空间的1/8
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight(); // getRowBytes()获取到图片每行有多少字节,乘以图片的高度就是图片占据的内存
}
};
/**
* 初始化其他
*/
mThreadPool = Executors.newFixedThreadPool(threadCount); // 初始化线程池threadPool
mPoolSemaphore = new Semaphore(threadCount); // 初始化线程池(消息队列)信号量
taskQueue = new LinkedList<Runnable>(); // 初始化任务队列taskQueue
mType = type == null ? Type.LIFO : type; // 同步Type
} /**
* 根据路径path找到对应的图片并异步加载到主界面中
*
* @param path 图片的路径
* @param imageView 目标的ImageView(图片将要被加载到的ImageView)
*/
public void loadImage(final String path, final ImageView imageView) {
imageView.setTag(path); // 将PATH设置为imageView的TAG,方便在主线程中对比、设置图片
// UI线程
if (uiHandler == null) {
uiHandler = 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;
// 将path与imageView的tag进行比较,如果相同,则为imageView设置图片
if (imageView.getTag().toString().equals(path)) {
imageView.setImageBitmap(bm);
}
}
};
} Bitmap bm = getBitmapFromLruCache(path); // 根据路径Path在缓存中获取Bitmap
if (bm != null) { // 如果这张图片存在于缓存中,则通知UI线程更新图片
ImgBeanHolder holder = new ImgBeanHolder();
holder.bitmap = bm;
holder.imageView = imageView;
holder.path = path;
Message message = Message.obtain();
message.obj = holder;
uiHandler.sendMessage(message);
} else { // 如果这张图片没有存在于缓存中,则添加一个新的任务到任务队列中
addTask(new Runnable() { // 添加一个任务到任务队列中
@Override
public void run() {
/**
* 加载图片
*/
// 压缩图片:1、获取图片需要显示的大小
ImageSize imageSize = getImageViewSize(imageView);
// 压缩图片:2、压缩图片
int reqWidth = imageSize.width;
int reqHeight = imageSize.height;
Bitmap bm = decodeSampledBitmapFromResource(path, reqWidth, reqHeight);
// 压缩图片:3、将图片加入到缓存
addBitmapToLruCache(path, bm);
// 将上面操作获取到的数据封装到ImgBeanHolder实体对象中,通知UI线程处理
ImgBeanHolder holder = new ImgBeanHolder();
holder.bitmap = getBitmapFromLruCache(path);
holder.imageView = imageView;
holder.path = path;
Message message = Message.obtain();
message.obj = holder;
uiHandler.sendMessage(message);
mPoolSemaphore.release(); // 为线程池释放一个信号量
}
});
}
} /**
* 添加一个任务到任务队列
* synchronized是为了避免多个线程同时到达这个方法中申请信号量而发生死锁
*/
private synchronized void addTask(Runnable runnable) {
/**
* 请求资源信号量,避免死锁
* 如果还没有对mPoolThreadHandler进行初始化,则默认的房间里可以容纳0个人,所以如果此时addTask()方法请求资源会被阻塞
* 通过这个信号量控制addTask()方法必须在mPoolThreadHandler初始化之后才调用
*/
try {
if (mPoolThreadHandler == null)
mPoolThreadHandlerSemaphore.acquire(); // 如果当前mPoolThreadHandler还没有初始化,则该线程阻塞(等待),直到mPoolThreadHandler初始化
} catch (InterruptedException e) {
e.printStackTrace();
}
taskQueue.add(runnable); // 添加Runnable任务到任务队列
mPoolThreadHandler.sendEmptyMessage(0x110); // 发送一个通知,通知后台轮询线程,0x110是一个随意的值
} /**
* 从任务队列中取出一个任务
* 根据为ImageLoader实例设定的图片加载策略决定是取出最后一个还是取出第一个
*/
private synchronized Runnable getTask() {
if (mType == Type.FIFO) {
return taskQueue.removeFirst();
} else if (mType == Type.LIFO) {
return taskQueue.removeLast();
}
return null;
} /**
* 根据ImageView获得适当的压缩的目标宽和高
*/
private ImageSize getImageViewSize(ImageView imageView) {
/**
* 获取ImageView的LayoutParams
*/
final DisplayMetrics metrics = imageView.getContext().getResources().getDisplayMetrics();
final LayoutParams lp = imageView.getLayoutParams();
/**
* 定义ImageView显示的宽度
*/
int width = lp.width == LayoutParams.WRAP_CONTENT ? 0 : imageView.getWidth(); // 获取ImageView的实际宽度
if (width <= 0) // ImageView可能是刚new出来就来执行这个方法,所以还没有宽高值,只能通过在layout中声明的宽高值来赋值
width = lp.width; // 获取ImageView在layout中声明的宽度
if (width <= 0) // 在layout中设置的宽高值是WRAP_CONTENT或MATCH_PARENT,则还是取出0,这时我们就需要看ImageView有没有设置max值
width = getImageViewFieldValue(imageView, "mMaxWidth"); // 通过反射获取ImageView的宽度最大值
if (width <= 0) // 可能ImageView没有设置max值,因此我们只能设置ImageView的宽或高为屏幕的宽或高
width = metrics.widthPixels;
/**
* 定义ImageView显示的高度(同宽度)
*/
int height = lp.height == LayoutParams.WRAP_CONTENT ? 0 : imageView.getHeight();
if (height <= 0) height = lp.height;
if (height <= 0) height = getImageViewFieldValue(imageView, "mMaxHeight");
if (height <= 0) height = metrics.heightPixels;
/**
* 将ImageView压缩后的宽和高封装到ImageSize实体类中返回
*/
ImageSize imageSize = new ImageSize();
imageSize.width = width;
imageSize.height = height;
return imageSize;
} /**
* 从LruCache中获取一张图片,如果不存在就返回null
*/
private Bitmap getBitmapFromLruCache(String key) {
return mLruCache.get(key);
} /**
* 将图片添加到LruCache缓存
*
* @param path 路径,先要判断图片是否已经在缓存中
* @param bitmap 图片
*/
private void addBitmapToLruCache(String path, Bitmap bitmap) {
if (getBitmapFromLruCache(path) == null) {
if (bitmap != null)
mLruCache.put(path, bitmap);
}
} /**
* 根据图片需求的宽高和图片实际的宽高计算inSampleSize(图片压缩的比例,用于压缩图片)
*
* @param options 图片实际的宽和高
* @param reqWidth 图片需要的宽度
* @param reqHeight 图片需要的高度
*/
private 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;
} /**
* 根据图片需要显示的宽和高对图片进行压缩
*
* @param path 图片的路径
* @param reqWidth 图片显示的宽度
* @param reqHeight 图片显示的高度
*/
private Bitmap decodeSampledBitmapFromResource(String path, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 如果该值设为true那么将不返回实际的bitmap,也不给其分配内存空间(避免内存溢出),但允许我们查询图片信息(包括图片大小信息)
BitmapFactory.decodeFile(path, options); // 经过这行代码,options就获得了图片实际的宽和高
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 调用上面定义的方法计算inSampleSize值(图片压缩的比例)
options.inJustDecodeBounds = false; // 设置可以取出图片
Bitmap bitmap = BitmapFactory.decodeFile(path, options); // 使用获取到的inSampleSize值再次解析图片,取出Bitmap
return bitmap;
} /**
* 存放图片的Bitmap源、ImageView和图片路径的实体类
*/
private class ImgBeanHolder {
Bitmap bitmap;
ImageView imageView;
String path;
} /**
* 存放ImageView显示的宽高的实体类
*/
private class ImageSize {
int width; // ImageView显示的宽度
int height; // ImageView显示的高度
} /**
* 通过反射获取某个Object的fieldName属性对应的值(本程序中是通过反射获得ImageView设置的最大宽度和高度)
*/
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
try {
Field field = ImageView.class.getDeclaredField(fieldName);
field.setAccessible(true);
int fieldValue = (Integer) field.get(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
value = fieldValue;
}
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
}

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

 package com.itgungnir.entity;

 /**
* PopUpWindow对应的Bean类
*/
public class FolderModel {
private String dir; // 图片的文件夹的路径
private String firstImgPath; // 第一张图片的路径
private String name; // 文件夹的名称
private int count; // 文件夹中图片的数量 public String getFirstImgPath() {
return firstImgPath;
} public void setFirstImgPath(String firstImgPath) {
this.firstImgPath = firstImgPath;
} public String getName() {
return name;
} public int getCount() {
return count;
} public void setCount(int count) {
this.count = count;
} public String getDir() {
return dir;
} public void setDir(String dir) {
this.dir = dir;
int lastIndex = this.dir.lastIndexOf("/");
this.name = this.dir.substring(lastIndex);
}
}

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

 package com.itgungnir.tools;

 import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.ImageView; import com.itgungnir.activity.R; import java.util.HashSet;
import java.util.List;
import java.util.Set; /**
* 主界面MainActivity中的GridView的适配器
*/
public class ImageAdapter extends BaseAdapter {
private static final Set<String> selectedImages = new HashSet<String>(); // 存储选中的图片的路径 private String dirPath; // GridView中图片的父路径
private List<String> imagePaths; // GridView中显示的图片的路径(s)
private LayoutInflater inflater; public ImageAdapter(Context context, List<String> data, String dirPath) {
this.dirPath = dirPath;
this.imagePaths = data;
inflater = LayoutInflater.from(context);
} @Override
public int getCount() {
return imagePaths.size();
} @Override
public Object getItem(int position) {
return imagePaths.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.sideworks_griditem, parent, false);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.find_griditem_iv_image);
viewHolder.checkBtn = (ImageButton) convertView.findViewById(R.id.find_griditem_ib_check);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
// 重置状态
viewHolder.imageView.setImageResource(R.mipmap.img_pictures_notfound);
viewHolder.checkBtn.setImageResource(R.mipmap.img_picture_unselected);
viewHolder.imageView.setColorFilter(null);
// 加载图片
ImageLoader.getInstance(3, ImageLoader.Type.LIFO).loadImage(dirPath + "/" + imagePaths.get(position), viewHolder.imageView); /**
* 为GridView的Item设置点击事件
*/
viewHolder.imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String imagePath = dirPath + "/" + imagePaths.get(position);
if (selectedImages.contains(imagePath)) { // 如果图片已经被选择,则清楚选择状态
selectedImages.remove(imagePath);
viewHolder.imageView.setColorFilter(null);
viewHolder.checkBtn.setImageResource(R.mipmap.img_picture_unselected);
} else { // 如果图片没有被选择,则选择图片
selectedImages.add(imagePath);
viewHolder.imageView.setColorFilter(Color.parseColor("#77000000")); // 设置图片上面覆盖一层透明的黑色蒙版
viewHolder.checkBtn.setImageResource(R.mipmap.img_pictures_selected); // 设置多选按钮为选中
}
}
}); return convertView;
} private class ViewHolder {
ImageView imageView;
ImageButton checkBtn;
}
}

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

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <ImageView
android:id="@+id/find_griditem_iv_image"
android:layout_width="match_parent"
android:layout_height="100.0dip"
android:scaleType="centerCrop"
android:src="@mipmap/img_pictures_notfound" /> <ImageButton
android:id="@+id/find_griditem_ib_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="3.0dip"
android:layout_marginTop="3.0dip"
android:background="@null"
android:clickable="false"
android:src="@mipmap/img_picture_unselected" /> </RelativeLayout>

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

 package com.itgungnir.view;

 import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.TextView; import com.itgungnir.activity.R;
import com.itgungnir.entity.FolderModel;
import com.itgungnir.tools.ImageLoader; import java.util.List; public class DirsPopWindow extends PopupWindow {
private int width; // PopUpWindow的宽度
private int height; // PopUpWindow的高度
private View convertView; // 代表PopUpWindow
private ListView dirList; // 显示目录信息的ListView
private List<FolderModel> datas; // ListView中显示的信息的列表 public OnDirSelectListener listener; public interface OnDirSelectListener {
void onDirSelected(FolderModel folder);
} public void setOnDirSelectListener(OnDirSelectListener listener) {
this.listener = listener;
} public DirsPopWindow(Context context, List<FolderModel> datas) {
calculateWidthAndHeight(context);
convertView = LayoutInflater.from(context).inflate(R.layout.sideworks_popwindow, null);
this.datas = datas;
setContentView(convertView);
setWidth(this.width);
setHeight(this.height);
setFocusable(true);
setTouchable(true);
setOutsideTouchable(true); // 外面可以点击
setBackgroundDrawable(new BitmapDrawable()); // 点击外面的区域会使PopUpWindow消失
// 点击外部的事件(让PopUpWindow消失)
setTouchInterceptor(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
dismiss();
return true;
}
return false;
}
}); initView(context);
initEvent();
} private void initView(Context context) {
dirList = (ListView) convertView.findViewById(R.id.find_popwindow_lv_dirs);
dirList.setAdapter(new DirAdapter(context, datas));
} private void initEvent() {
dirList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (listener != null) {
listener.onDirSelected(datas.get(position));
}
}
});
} /**
* 计算PopUpWindow的宽度和高度
*/
private void calculateWidthAndHeight(Context context) {
// 获取屏幕的宽和高
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics);
// 为PopUpWindow设置宽和高
this.width = metrics.widthPixels;
this.height = (int) (metrics.heightPixels * 0.7);
} /**
* PopUpWindow中ListView的适配器
*/
private class DirAdapter extends ArrayAdapter<FolderModel> {
private LayoutInflater inflater;
private List<FolderModel> datas; public DirAdapter(Context context, List<FolderModel> objects) {
super(context, 0, objects);
inflater = LayoutInflater.from(context);
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = inflater.inflate(R.layout.sideworks_popitem, parent, false);
viewHolder.image = (ImageView) convertView.findViewById(R.id.find_popitem_iv_image);
viewHolder.dirName = (TextView) convertView.findViewById(R.id.find_popitem_tv_dir);
viewHolder.imgCount = (TextView) convertView.findViewById(R.id.find_popitem_tv_imgcount);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
} FolderModel folder = getItem(position);
// 重置
viewHolder.image.setImageResource(R.mipmap.img_pictures_notfound);
// 回调加载
ImageLoader.getInstance(3, ImageLoader.Type.LIFO).loadImage(folder.getFirstImgPath(), viewHolder.image);
viewHolder.dirName.setText(folder.getName());
viewHolder.imgCount.setText(folder.getCount() + ""); return convertView;
} private class ViewHolder {
ImageView image;
TextView dirName;
TextView imgCount;
}
}
}

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

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"> <ListView
android:id="@+id/find_popwindow_lv_dirs"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/cl_white"
android:cacheColorHint="@color/cl_transparent"
android:divider="#EEE3D9"
android:dividerHeight="1.0dip" /> </LinearLayout>

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

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="120.0dip"
android:background="@color/cl_white"
android:padding="10.0dip"> <ImageView
android:id="@+id/find_popitem_iv_image"
android:layout_width="100.0dip"
android:layout_height="100.0dip"
android:layout_centerVertical="true"
android:background="@mipmap/img_pic_dir_bg"
android:contentDescription="@string/app_name"
android:paddingBottom="17.0dip"
android:paddingLeft="12.0dip"
android:paddingRight="12.0dip"
android:paddingTop="9.0dip"
android:scaleType="fitXY" /> <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20.0dip"
android:layout_toRightOf="@+id/find_popitem_iv_image"
android:orientation="vertical"> <TextView
android:id="@+id/find_popitem_tv_dir"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/cl_black"
android:textSize="16.0sp" /> <TextView
android:id="@+id/find_popitem_tv_imgcount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/darker_gray"
android:textSize="14.0sp" />
</LinearLayout> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:src="@mipmap/img_dir_choosen" /> </RelativeLayout>

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

 <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> <translate
android:duration="200"
android:fromXDelta="0"
android:fromYDelta="100%"
android:toXDelta="0"
android:toYDelta="0" /> </set>

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

 <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> <translate
android:duration="200"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="0"
android:toYDelta="100%" /> </set>

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

 <resources>

     <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style> <style name="Style_Main_TextView">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_centerVertical">true</item>
<item name="android:textColor">@android:color/white</item>
<item name="android:textSize">18.0sp</item>
<item name="android:textStyle">bold</item>
</style> <style name="Style_PopWindow_Anim">
<item name="android:windowEnterAnimation">@anim/slide_up</item>
<item name="android:windowExitAnimation">@anim/slide_down</item>
</style> </resources>

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

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.itgungnir.activity"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@android:style/Theme.NoTitleBar">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application> </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. 项目自动化建构工具gradle 入门5——在intellij中做一个gradle的web工程

    之前的几个小节,都是纯手工建文件夹,纯手工拷贝war包,或者纯手工解压个zip看看结果,,,,这还是我写了玩的helloWorld.若是玩大工程.几十个人的团队协同开发时,这么玩,,,,暴躁的程序员估 ...

  2. js给数组去重写法

    数组为 var list =['A','B','A']; 法一:常规做法,新建list,给list添加元素,添加前判断是否包含 var removeRepeatItem = function(list ...

  3. (一)java arcgis开发环境搭建

    一,整个开发环境 OS:Win7 Development: eclipse 4.3.2 框架:spring+springMVC+mybatis+jquery Arcgis版本:10.2 desktop ...

  4. Hemodynamic response function (HRF) - FAQ

    Source: MIT - Mindhive What is the 'canonical' HRF? The very simplest design matrix for a given expe ...

  5. 细说 Form (表单)

    细说 Form (表单) Form(表单)对于每个WEB开发人员来说,应该是再熟悉不过的东西了,可它却是页面与WEB服务器交互过程中最重要的信息来源. 虽然Asp.net WebForms框架为了帮助 ...

  6. [LeetCode] Arithmetic Slices II - Subsequence 算数切片之二 - 子序列

    A sequence of numbers is called arithmetic if it consists of at least three elements and if the diff ...

  7. [LeetCode] Encode and Decode Strings 加码解码字符串

    Design an algorithm to encode a list of strings to a string. The encoded string is then sent over th ...

  8. 做中学(Learning by Doing)之背单词-扇贝网推荐

    做中学(Learning by Doing)之背单词-扇贝网推荐 看完杨贵福老师(博客,知乎专栏,豆瓣)的「继续背单词,8个月过去了」,我就有写这篇文章的冲动了,杨老师说: 有时候我会感觉非常后悔,如 ...

  9. 大于16MB的QSPI存放程序引起的ZYNQ重启风险

    ZYNQ芯片是近两年比较流行的片子,双ARM+FPGA,在使用分立FPGA和CPU的场合很容易替代原来的分立器件. ZYNQ可以外接QSPI FLASH作为程序的存储介质. QSPI和SPI flas ...

  10. react 表单

    import React ,{PropTypes}from 'react'; import { render } from 'react-dom'; const styles={ mb10:{ mar ...