很久之前项目中用到的一个图片选择功能,于是当时做一个简单的demo。测试了一下,支持图片的选择,预览,删除等基本操作,支持图片的倒序显示,拍照添加图片,以及过滤较小图片等。
部分截图,如下
功能描述:
1. 点击添加图片按钮进入图片选择页面,选择的图片显示在主界面,并且可以删除。
2. 在图片选择页面可以点击chechbox选择图片,点击图片进行预览,可靠图片有个数限制。
3. 点击左下方按钮进入相册选择列表,可以浏览不同相册。
4. 点击拍照按钮可以进行拍照并且将拍照图片直接显示在主界面。
5. 预览界面可以进行图片选择,点击完成所选图片显示在主界面,若无已选图片,则预览图片默认被选择。
主要代码:
最重要的代码就是对手机图片的扫描,这里是利用ContentProvider,然后将获取到的图片按照名称为key保存到一个map,测试中发现微信是将文件夹名称相同的图片列为同一相册,而qq是根据不同的id列为同一相册。同时自定义了一个全部图片的相册,用来保存所有的图片并且过滤掉了较小的图片(微信的默认列表就对图片进行了过滤),这样就避免了有些应用产生的垃圾图片显示在主界面(如一堆空白图)。详细代码:
public class AlbumHelper extends AsyncTask<Object, Object, Object>{
final String TAG = getClass().getSimpleName();
//过滤掉非常小的图片的参数(微信的默认图片列表貌似就是过滤掉了较小的图片)
public static int mMinImageSize = 5000;//5000b
Context context;
ContentResolver cr;
// 专辑列表
LinkedHashMap<String, PhotoImageBucket> bucketList = new LinkedHashMap<>();//保证相册的顺序与输入相同
private GetAlbumList getAlbumList;
private String allPhotosBucketId = "-11111111111";//自定义全部图片的bucketId
/**
* 初始化
* @param context
*/
public void init(Context context) {
if (this.context == null) {
this.context = context;
cr = context.getContentResolver();
}
}
/**
* 是否创建了图片集
*/
boolean hasBuildImagesBucketList = false;
/**
* 得到图片集
*/
private void buildImagesBucketList() {
// 构造相册索引
String columns[] = new String[] { Media._ID, Media.BUCKET_ID,
Media.PICASA_ID, Media.DATA, Media.DISPLAY_NAME, Media.TITLE,
Media.SIZE, Media.BUCKET_DISPLAY_NAME };
//按时间倒序
String sortOrder = Media.DATE_ADDED + " DESC";
// 得到一个游标
Cursor cur = cr.query(Media.EXTERNAL_CONTENT_URI, columns, null, null,
sortOrder);
if (cur == null) {
LogUtils.logd(TAG, "call: " + "Empty images");
}else
if (cur.moveToFirst()) {
//设置存放所有图片的相册,第一个入存放相册的map
PhotoImageBucket allBucket = new PhotoImageBucket();
allBucket.setImageList(new ArrayList<PhotoImageItem>());
allBucket.setBucketName("所有图片");
bucketList.put(allPhotosBucketId, allBucket);
// 获取指定列的索引
int photoIDIndex = cur.getColumnIndexOrThrow(Media._ID);
int photoPathIndex = cur.getColumnIndexOrThrow(Media.DATA);
int bucketDisplayNameIndex = cur.getColumnIndexOrThrow(Media.BUCKET_DISPLAY_NAME);
int photoSizeIndex = cur.getColumnIndexOrThrow(Media.SIZE);
int bucketIdIndex = cur.getColumnIndexOrThrow(Media.BUCKET_ID);
do {
//过滤掉名字为空的图片,如.jpg,.png等
if (cur.getString(photoPathIndex).substring(
cur.getString(photoPathIndex).lastIndexOf("/")+1,
cur.getString(photoPathIndex).lastIndexOf("."))
.replaceAll(" ", "").length()<=0)
{
LogUtils.logd(TAG, "出现了异常图片的地址:cur.getString(photoPathIndex)="+cur.getString(photoPathIndex));
}else {
String _id = cur.getString(photoIDIndex);
String path = cur.getString(photoPathIndex);
String bucketName = cur.getString(bucketDisplayNameIndex);
int photoSize = cur.getInt(photoSizeIndex);
//以相册句为key将相册名相册的图片放入同一个相册,如果想不同相册分开显示可以使用bucketId为key
//String bucketId = cur.getString(bucketIdIndex);
//PhotoImageBucket bucket = bucketList.get(bucketId);
PhotoImageBucket bucket = bucketList.get(bucketName);
//这里完成图片归并到相应的相册里去
if (bucket == null) {
bucket = new PhotoImageBucket();
bucketList.put(bucketName, bucket);//使用bucketName进行合并,将名字相同的保存到同一相册
bucket.setImageList(new ArrayList<PhotoImageItem>());
bucket.setBucketName(bucketName);
}
bucket.setCount(bucket.getCount() + 1);
PhotoImageItem imageItem = new PhotoImageItem();
imageItem.setImageId(_id);
imageItem.setImagePath(path);
bucket.getImageList().add(imageItem);
//将每一张图片都存入所有图片的相册里
LogUtils.loge(TAG, path + "");
if(photoSize > mMinImageSize){//所有图片里过滤掉较小图片,避免显示过多无效图片(空图)---测试微信中进行了过滤,不知道过滤规则
allBucket.setCount(allBucket.getCount() + 1);
allBucket.getImageList().add(imageItem);
}
}
} while (cur.moveToNext());
}
cur.close();
hasBuildImagesBucketList = true;
}
/**
* 得到图片集
* @param refresh
* @return tmpList
*/
private List<PhotoImageBucket> getImagesBucketList(boolean refresh) {
if (refresh || (!refresh && !hasBuildImagesBucketList)) {
buildImagesBucketList();
}
List<PhotoImageBucket> tmpList = new ArrayList<>();
Iterator<Entry<String, PhotoImageBucket>> itr = bucketList.entrySet().iterator();
//将Hash转化为List
while (itr.hasNext()) {
Map.Entry<String, PhotoImageBucket> entry = itr
.next();
tmpList.add(entry.getValue());
}
return tmpList;
}
public void setGetAlbumList(GetAlbumList getAlbumList) {
this.getAlbumList = getAlbumList;
}
//回调接口
public interface GetAlbumList{
void getAlbumList(List<PhotoImageBucket> list);
}
@Override
protected Object doInBackground(Object... params) {
return getImagesBucketList((Boolean)(params[0]));
}
@SuppressWarnings("unchecked")
@Override
protected void onPostExecute(Object result) {
super.onPostExecute(result);
getAlbumList.getAlbumList((List<PhotoImageBucket>) result);
}
}
以上代码中出现的PhotoImageBucket ,与PhotoImageItem 是创建用来存放相册信息与图片信息的bean,这里就不贴代码了,结尾会附上全部代码。
图片选择界面是一个gridView用来显示图片以及listView用来显示相册列表(可用popupWindow个人喜好),在gridView中的图片列表上添加了一个遮罩层,避免图片与checkbos颜色相同相互影响,另外当选中图片后将图片置暗。gridView的adapter中添加了两个回调监听,分别是点击图片预览与点击checkbox选择,代码如下:
public class AlbumGridViewAdapter extends BaseAdapter{
final String TAG = getClass().getSimpleName();
private LayoutInflater inflater;
private ArrayList<PhotoImageItem> dataList;
private ArrayList<PhotoImageItem> selectedDataList;
private BitmapUtils mBitmapUtils;
public AlbumGridViewAdapter(Context context, ArrayList<PhotoImageItem> dataList,
ArrayList<PhotoImageItem> selectedDataList) {
inflater = LayoutInflater.from(context);
this.dataList = dataList;
this.selectedDataList = selectedDataList;
mBitmapUtils = new BitmapUtils(context);
mBitmapUtils.configDefaultLoadingImage(R.mipmap.album_default_loading_pic);//默认背景图片
mBitmapUtils.configDefaultLoadFailedImage(R.mipmap.album_default_loading_pic);//加载失败图片
mBitmapUtils.configDefaultBitmapConfig(Bitmap.Config.RGB_565);//设置图片压缩类型
}
public int getCount() {
return dataList.size();
}
public Object getItem(int position) {
return dataList.get(position);
}
public long getItemId(int position) {
return 0;
}
private static class ViewHolder {
public SquareImageView imageView;
public CheckBox checkBox;
public View maskView;
public LinearLayout ll;
}
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = inflater.inflate(R.layout.album_pic_item, parent, false);
viewHolder.imageView = (SquareImageView)convertView.findViewById(R.id.item_selected_image);
viewHolder.maskView = convertView.findViewById(R.id.image_mask);
viewHolder.checkBox = (CheckBox)convertView.findViewById(R.id.album_checkbox);
viewHolder.ll = (LinearLayout)convertView.findViewById(R.id.album_item_ll);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
if(selectedDataList.contains(dataList.get(position))){
viewHolder.checkBox.setChecked(true);
viewHolder.maskView.setBackgroundResource(R.color.image_select_mask);
}else{
viewHolder.checkBox.setChecked(false);
viewHolder.maskView.setBackgroundResource(R.color.image_default_mask);
}
viewHolder.imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (dataList != null && mOnPhotoClickListener != null
&& position < dataList.size()) {
mOnPhotoClickListener.onItemClick(position);
}
}
});
viewHolder.ll.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (dataList != null && mOnCheckBoxClickListener != null
&& position < dataList.size()) {
mOnCheckBoxClickListener.onItemClick(viewHolder.maskView, position, viewHolder.checkBox);
}
}
});
mBitmapUtils.display(viewHolder.imageView, dataList.get(position).getImagePath());
return convertView;
}
private OnPhotoClickListener mOnPhotoClickListener;
public void setOnPhotoClickListener(OnPhotoClickListener l){
mOnPhotoClickListener = l;
}
//预览图片的回调监听
public interface OnPhotoClickListener{
void onItemClick(int position);
}
private OnCheckBoxClickListener mOnCheckBoxClickListener;
public void setOnCheckBoxClickListener(OnCheckBoxClickListener l){
mOnCheckBoxClickListener = l;
}
//选择图片的预览监听
public interface OnCheckBoxClickListener{
void onItemClick(View maskView, int position, CheckBox checkBox);
}
}
关于拍照功能的实现代码如下:
public void photo() {
if(android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
//照片的路径
filePath = Environment.getExternalStorageDirectory()
+ "/albumtest/" + "testPIC/";
//照片名
fileName = "albumtestpic." + String.valueOf(System.currentTimeMillis()) + ".jpg";
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
photoUri = Uri.fromFile(new File(filePath, fileName));
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(intent, TAKE_PICTURE);
} else {
Toast.makeText(AlbumActivity.this, "内存卡不可用,不能拍照", Toast.LENGTH_LONG).show();
}
}
这里通过intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);修改了图片的保存路径,然后在onActivityResult进行处理。这里有一点需要注意,当拍照完成之后一定要对该照片进行扫描,否则你将在图片列表中找不到你所拍摄的照片(测试微信等应该也无法显示该照片),代码如下:
MediaScannerConnection.scanFile(this,
new String[]{filePath + fileName}, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
}
});
相册列表的实现比较简单这里就不再赘述。图片预览界面就是一个viewPager也比较简单,只是在PagerAdapter的instantiateItem中进行加载图片,避免一次性添加很多图片引起oom。另外关于图片选择个数的限制,以及点击完成图片显示在主界面等功能在这里也不再赘述,大家可以在全部代码中查看。
项目结构:
由于本例只是个demo,并没有进行测试,肯定会存在一些bug,例如图片加载的oom,在这里有很多框架例如Android-Universal-Image-Loader,xUtils等,当然你也可以自己去设置图片的
inSampleSize,缓存以及存储路径。demo当时使用的是xUtils较低版本。另外拍照功能的实现在有些手机上也会出现异常,在此demo上并没有进行测试。
具体内容,请查看源码。