ギャラリーから画像をまとめて表示したい場合、ContentResolverを使って画像のThumbnails情報を取ってきて表示するのが一般的かと思います。
が、注意すべき点として、Thumbnails画像には回転情報が含まれていないため、端末の機種によっては縦横の表示がおかしくなるケースがあります。
具体的にはGalaxy S4以降では縦長画像が横向きになってしまう現象が確認されました。
解決方法
ContentResolverの MediaStore.Images.Media._ID
と MediaStore.Images.Thumbnails.IMAGE_ID
が同じことを利用し、MediaStore.Images.Mediaの回転情報を取得します。表示する際はこの回転情報を元にbitmapを回転して表示してあげます。PicassoやGlideを使えば画像回転をよろしくやってくれる(と、いうかPicassoでは内部的にMediaStore.Imagesの回転情報を取得する機構があるっぽい)かもしれませんが、今回は割愛します。
CursorJoinerをカスタマイズする
一致するMediaStore.Images.Media._ID
と MediaStore.Images.Thumbnails.IMAGE_ID
を抽出するにはCursorJoinerを使います。
CursorJoinerはその名の通りCursorをjoinします。内部的には2つのCursorを比較し、一致する項目が出てくるまでそれぞれのCursorを順に進めていくようになっています。ただしString型でしか比較してくれません。そのため、IDのint型でも対応できるようにCursorJoinerをカスタマイズします。実装方法はここのブログ(なんと5年前!)にも書いてあるのですが、写真の一覧の場合新しいものを先頭に持ってきたいので降順(DESC)で比較できるように工夫します。
実際の実装はGistに貼っています。
変更点をかいつまんで書くと
(1)型と比較順序に関するenum追加
/**
* The type of column to join
*/
public enum JoinColumnType {
STRING,
INT
}
/**
* Order
*/
public enum OrderType {
ASC, DESC;
}
//コンストラクタ内
mType = type;
switch (mType) {
case STRING:
mStringValues = new String[mColumnsLeft.length * 2];
break;
case INT:
mIntValues = new int[mColumnsLeft.length * 2];
}
sOrderType = orderType;
(2) 型によって比較方法を分岐させる
CursorJoinerはIteratorになっています。next()メソッドで順次Cursorを移動させて比較するわけですが、型によって処理を分岐させます。
public Result next() {
...
if (hasLeft && hasRight) {
int compareResult = 0;
switch (mType){
case STRING:
populateStringValues(mStringValues, mCursorLeft, mColumnsLeft, 0);
populateStringValues(mStringValues, mCursorRight, mColumnsRight, 1);
compareResult = compareStrings(mStringValues);
break;
case INT:
populateIntValues(mIntValues, mCursorLeft, mColumnsLeft, 0);
populateIntValues(mIntValues, mCursorRight, mColumnsRight, 1);
compareResult = compareInts(mIntValues);
break;
int型の比較(compareInts()
)は以下のようになります。OrderTypeによって返る値が変わる(どちらのCursorを進めるか決める)ことがわかります。
private static int compareInts(int... values) {
if ((values.length % 2) != 0) {
throw new IllegalArgumentException("you must specify an even number of values");
}
for (int index = 0; index < values.length; index += 2) {
int comp = values[index] - values[index + 1];
if (comp != 0) {
if (sOrderType == OrderType.DESC) {
// if LEFT is smaller than RIGHT, move RIGHT cursor since value order is desc
return comp < 0 ? 1 : -1;
} else if (sOrderType == OrderType.ASC) {
return comp > 0 ? 1 : -1;
}
}
}
return 0;
}
使い方
このCustomCursorJoinerは以下のように使います。
private static final Uri THUMB_URI = MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI;
private static final String[] THUMB_PROJECTION = {MediaStore.Images.Thumbnails.DATA,MediaStore.Images.Thumbnails.IMAGE_ID};
private static final String[] THUMB_WHERE = {String.valueOf(MediaStore.Images.Thumbnails.MINI_KIND)};
private static final String THUMB_ORDER_BY = MediaStore.Images.Thumbnails.IMAGE_ID + " DESC";
private static final Uri IMAGE_MEDIA_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
private static final String IMAGE_ORDER_BY = MediaStore.Images.Media._ID + " DESC";
private static final String[] CONTENT_ORIENTATION = new String[] {
MediaStore.Images.Media._ID,
MediaStore.Images.ImageColumns.ORIENTATION
};
private static final String IMAGE_ORDER_BY = MediaStore.Images.Media._ID + " DESC";
String selection = MediaStore.Images.Thumbnails.KIND + " = ? AND "
+ MediaStore.Images.Thumbnails.IMAGE_ID + " IN (" + TextUtils.join(",", imageIdList) + ")";
ContentResolver resolver = context.getContentResolver();
Cursor thumbNailCursor = resolver.query(
THUMB_URI,
THUMB_PROJECTION,
selection,
THUMB_WHERE,
THUMB_ORDER_BY);
String rotationSelection = MediaStore.Images.Media._ID + " IN (" + TextUtils.join(",", imageIdList) + ")";
Cursor rotationCursor = resolver.query(
IMAGE_MEDIA_URI,
CONTENT_ORIENTATION,
rotationSelection,
null,
IMAGE_ORDER_BY);
CustomCursorJoiner joiner = new CustomCursorJoiner(
thumbNailCursor,
new String[]{MediaStore.Images.Thumbnails.IMAGE_ID},
rotationCursor,
new String[]{MediaStore.Images.Media._ID},
CustomCursorJoiner.JoinColumnType.INT,
CustomCursorJoiner.OrderType.DESC
);
for(CustomCursorJoiner.Result result : joiner){
if(result == CustomCursorJoiner.Result.BOTH){
PhotoItem item = new PhotoItem(); // 写真情報(thumbnail, id, 回転情報)を管理するオブジェクト
item.setThumbNailPath(CursorUtil.getString(thumbNailCursor, MediaStore.Images.Thumbnails.DATA));
item.setId(CursorUtil.getInt(thumbNailCursor, MediaStore.Images.Thumbnails.IMAGE_ID));
item.setRotation(CursorUtil.getInt(rotationCursor, MediaStore.Images.ImageColumns.ORIENTATION));
resultList.add(item);
}
}
rotationCursor.close();
thumbNailCursor.close();
ありがたいことにMediaStore.Images.ImageColumns.ORIENTATIONはdegreeで返ってきます。扱いやすいですね。
あとは以下のようにBitmap生成時にrotation情報を加えてあげて、imageView等にsetしてあげればOKです。
Bitmap bitmap;
bitmap = BitmapFactory.decodeFile(item.getThumbNailPath(), options);
int degree = item.getRotation();
if (degree > 0F) {
Matrix matrix = new Matrix();
matrix.postRotate(degree);
Bitmap rotateBm;
rotateBm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
bitmap.recycle();
bitmap = rotateBm;
}
...