fred-ye / summary

my blog
43 stars 9 forks source link

Android图片选择器-1 #61

Open fred-ye opened 6 years ago

fred-ye commented 6 years ago

最近因为工作上的需要看了一些网上开源的图片选择器,有大厂如知乎出品的Matisse, 也有一些个人开发者写的。看完后发现,因为这个功能是和具体的产品设计风格相关,所以网上的图片选择器很多情况下是不能满足我们的实际项目开发。比如UI设计差距较大,有的图片选择器不支持预览网络图片,不支持全屏(沉浸式导航)。所以我们的做法通常是参考网上的一个图片选择器,然后再自己根据产品设计修改相应UI,添加一些新的功能。有时候因为项目的进度原因,部份代码都没有仔细看。刚好最近有打算梳理一下自己的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的介结可以看这里, 简单来讲,loaderAsyncTask的功能更强大,它能够保留并缓存数据,避免configuration变化后的重复查询。Loaders的使用需要熟悉两个类LoaderLoaderManager。 [虽然说Google 在android P中已经废弃了Loaders, 建议用LiveData和ViewModels来代替,但我的代码已经写了,就还是先用这个吧,再说一时半会公司怕也不会用Google的这一套新的,后面自己会考虑用LiveDataViewModels重构]

LoaderManger

每个Activity 或 Fragment中都可以通过getSupportLoaderManager()来获取一个唯一的LoaderManager, 而这个LoaderManager可以管理多个Loader

LoaderManager.LoaderCallbacks

这是Loader运行时的回调,它包含三个方法onCreateLoader, onLoadFinished, onLoaderReset。通常由调用了initLoader, restartLoader方法的Activity或Fragment来实现这个接口

Loader

Loader会执行具体的数据加载操作,它有两个子类,AsyncTaskLoaderCursorLoader, 其中的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