Android图片选择器-1
最近因为工作上的需要看了一些网上开源的图片选择器,有大厂如知乎出品的Matisse, 也有一些个人开发者写的。看完后发现,因为这个功能是和具体的产品设计风格相关,所以网上的图片选择器很多情况下是不能满足我们的实际项目开发。比如UI设计差距较大,有的图片选择器不支持预览网络图片,不支持全屏(沉浸式导航)。所以我们的做法通常是参考网上的一个图片选择器,然后再自己根据产品设计修改相应UI,添加一些新的功能。有时候因为项目的进度原因,部份代码都没有仔细看。刚好最近有打算梳理一下自己的Android体系结构的想法,就想着自己重写一个图片选择器,借此来梳理一下相关的知识点。
这里想要实现的图片选择器具体的需求我就不列出来了,直接脑补,或者看最终的成品。这里先来列举一下这个功能所需要的知识点
数据相关:
- 如何从Android系统中查询所有的相册
- 如何查询某个相册中的所有图片,并排序
- 如何将最新拍摄的照片插入到系统的图库中,然后查出来再显示。
- 选中的照片的存储
UI显示相关:
- 相册列表的显示
- 每个相册中图片的显示
- 选中相册后图片列表需要对应的刷新
- 大图的预览
- 图片的选择与取消选择
- 沉浸式导航
其它
- 接口的封装
- 样式的定义
再来看一下具体的技术点,先看数据相关,相册的查询,图片的查询,我们需要用到Content Provider, 同时考虑到它是一个耗时的操作,我们采用CursorLoader来进行处理。我们先来看一下从Content Provider中获取我们想要的数据:
获取相册相关的信息
我们的目的是系统中的每一个相册的名称,该相册有多少张图片。同时考虑UI因素,我们需要拿一个图片作为该相册的封面。从显示的效果上看,只要这几个数据就可以了。
对于相册,图片相关的信息,Android是采用Content Provider为我们提供数据,Content Provider相关的操作在这里, 我们先来看一下查询相册,直接上代码:
private static final String COLUMN_COUNT = "count";
private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");
private static final String BUCKET_ORDER_BY = MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC";
private static final String[] PROJECTION = {
MediaStore.Files.FileColumns._ID,
MediaStore.Images.ImageColumns.BUCKET_ID,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.MediaColumns.DATA,
"COUNT(*) AS " + COLUMN_COUNT
};
public void searchAllAlbum() {
String args[] = new String[]{String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE)};
Cursor albums = getContentResolver().query(QUERY_URI, ALBUM_PROJECTION, ALBUM_SELECTION, args, ORDER_BY_DATE);
int totalCount = 0;
if (albums != null) {
while (albums.moveToNext()) {
totalCount += albums.getInt(albums.getColumnIndex(COLUMN_COUNT));
Log.i("MainActivity", "id:" + albums.getInt(albums.getColumnIndex(MediaStore.Images.ImageColumns.BUCKET_ID)));
Log.i("MainActivity", "displayName:" + albums.getString(albums.getColumnIndex(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME)));
Log.i("MainActivity"," pic:" + albums.getString(albums.getColumnIndex(MediaStore.MediaColumns.DATA)));
}
albums.close();
};
}
通过以上代码,我们可以查出相册的id, display name, 封面以及相册中的图片的个数。
根据相册获取里面的图片数据
private static final String[] IMAGE_PROJECTION = {
MediaStore.MediaColumns._ID,
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.SIZE,
MediaStore.MediaColumns.DATA
};
private static final String IMAGE_SELECTION =
MediaStore.Files.FileColumns.MEDIA_TYPE + " = ? "
+ " AND "
+ " bucket_id= ? "
+ " AND " + MediaStore.MediaColumns.SIZE + " > 0";
private static String [] getImageSelectionArgs(String albumId) {
return new String[]{
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
albumId
};
};
//查询:
public void searchImage() {
//传入相册的id
String args[] = getImageSelectionArgs("-1617409521");
Cursor images = getContentResolver().query(QUERY_URI, IMAGE_PROJECTION, IMAGE_SELECTION, args, ORDER_BY_DATE);
if (images != null) {
while (images.moveToNext()) {
Log.i(TAG, "id:" + images.getInt(images.getColumnIndex(MediaStore.MediaColumns._ID)));
Log.i(TAG, "displayName:" + images.getString(images.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)));
Log.i(TAG, "mimeType:" + images.getString(images.getColumnIndex(MediaStore.Images.ImageColumns.MIME_TYPE)));
Log.i(TAG, "size:" + images.getString(images.getColumnIndex(MediaStore.Images.ImageColumns.SIZE)));
//该属性返回的是图片在sd卡上的物理路径
Log.i(TAG," data:" + images.getString(images.getColumnIndex(MediaStore.MediaColumns.DATA)));
}
}
}
获取缩略图相关的数据
Android系统在创建图片的时候,会为图片生成一个缩略图,查询缩略图的方法:
String[] projection = new String[] {MediaStore.Images.Thumbnails._ID, MediaStore.Images.Thumbnails.DATA};
Cursor thumbnails = getContentResolver().query(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, projection, null, null, null);
if (thumbnails != null) {
while(thumbnails.moveToNext()) {
String data = thumbnails.getString(thumbnails.getColumnIndex(MediaStore.Images.Thumbnails.DATA));
Log.i(TAG, "data:" + data);
}
}
将最新拍摄的照片添加进图库
private void galleryAddPic() {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(mCurrentPhotoPath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}
以上代码参看这里
使用CursorLoader进行数据的查询
考虑到图片的查询是一个IO操作,我们需要做异步的查询, 需要自己写AsyncTask或子线程进行处理。而实际上Google为我们提供了Loaders来处理这种情况,关于Loaders的介结可以看这里, 简单来讲,loader比AsyncTask的功能更强大,它能够保留并缓存数据,避免configuration变化后的重复查询。Loaders的使用需要熟悉两个类Loader和LoaderManager。
[虽然说Google 在android P中已经废弃了Loaders, 建议用LiveData和ViewModels来代替,但我的代码已经写了,就还是先用这个吧,再说一时半会公司怕也不会用Google的这一套新的,后面自己会考虑用LiveData和ViewModels重构]
LoaderManger
每个Activity 或 Fragment中都可以通过getSupportLoaderManager()来获取一个唯一的LoaderManager, 而这个LoaderManager可以管理多个Loader
LoaderManager.LoaderCallbacks
这是Loader运行时的回调,它包含三个方法onCreateLoader, onLoadFinished, onLoaderReset。通常由调用了initLoader, restartLoader方法的Activity或Fragment来实现这个接口
Loader
Loader会执行具体的数据加载操作,它有两个子类,AsyncTaskLoader和CursorLoader, 其中的CursorLoader是用来从ContentProvider中加载数据,它返回一个Cursor
用一个Demo来看一下具体的使用, 使用CursorLoader来查询相册:
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{
public static final String TAG = "MainActivity";
public static final String COLUMN_COUNT = "count";
private static final String ORDER_BY_DATE = MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC";
private static final String ALBUM_SELECTION =
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
+ ") GROUP BY ("+ MediaStore.Images.ImageColumns.BUCKET_ID;
private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");
private static final String[] ALBUM_PROJECTION = {
MediaStore.MediaColumns._ID,
MediaStore.Images.ImageColumns.BUCKET_ID,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.MediaColumns.DATA,
"COUNT(*) AS " + COLUMN_COUNT};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//
getSupportLoaderManager().initLoader(0, null, this);
}
@Override
public Loader onCreateLoader(int id, Bundle args) {
String selectionArgs[] = new String[]{String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE)};
return new CursorLoader(this, QUERY_URI, ALBUM_PROJECTION, ALBUM_SELECTION, selectionArgs, ORDER_BY_DATE);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor albums) {
if (albums != null) {
while (albums.moveToNext()) {
Log.i(TAG, "id:" + albums.getInt(albums.getColumnIndex(MediaStore.Images.ImageColumns.BUCKET_ID)));
Log.i(TAG, "displayName:" + albums.getString(albums.getColumnIndex(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME)));
Log.i(TAG," pic:" + albums.getString(albums.getColumnIndex(MediaStore.MediaColumns.DATA)));
}
};
}
@Override
public void onLoaderReset(Loader loader) {
}
}
以上是一些零碎的关于图片数据的技术点,记录下来供以后参考。接下来我们再梳理一下相关UI界面的开发 本文同步更新到 http://fredye.cn/index.php/archives/android-image-picker-1.html