2013年1月26日土曜日

NotificationのBigPictureStyleで画像全体を表示させてみる

BigPictureStyleでは、画像の中央を中心にして、最大幅450dpに合う大きさへ画像がクロップされる。

でもそれだと、画像が縦長の場合は上下が大きくクロップされてしまい、何が写っているのかよく分からない。



画像の比率によらず、bigPicture部分で、常に画像全体が見られるようにしてみた。
private Bitmap getBigPicture(Resources res, String path) {
 Bitmap canvasBmp = null;

 final float WIDTH_DP = 450.0f;
 final float HEIGHT_DP = 192.0f;

 // ピクセル密度を取得する
 DisplayMetrics metrics = new DisplayMetrics();
 getWindowManager().getDefaultDisplay().getMetrics(metrics);
 float scaleDensity = metrics.scaledDensity;

 // ピクセル密度を加味してターゲットの幅と高さのピクセル数を計算する
 int targetWidthPx = (int) (WIDTH_DP * scaleDensity);
 int targetHeightPx = (int) (HEIGHT_DP * scaleDensity);

 // 画像読み込みオプションを作成
 BitmapFactory.Options options = new BitmapFactory.Options();
 // メモリが少なくなったらパージできるようにオプションを設定
 options.inPurgeable = true;
 // 画像サイズだけを読み込むようにオプションを設定
 options.inJustDecodeBounds = true;

 // 画像読み込み
 Bitmap srcBmp = BitmapFactory.decodeFile(path, options);

 // 画像の幅と高さのピクセル数を取得する
 int srcWidth = options.outWidth;
 int srcHeight = options.outHeight;

 // Matrixを取得する パディングは0で小さな画像は拡大しない
 Matrix mat = getMatrix(srcWidth, srcHeight, targetWidthPx, targetHeightPx, 0, false);

 // Paintを作成する
 Paint bmpPaint = new Paint();
 bmpPaint.setFilterBitmap(true);

 // ターゲット幅と高さで背景が透明のキャンバスを作成
 canvasBmp = Bitmap.createBitmap(targetWidthPx, targetHeightPx, Bitmap.Config.ARGB_8888);
 Canvas canvas = new Canvas(canvasBmp);

 // 画像サイズだけでなく画像そのものを読み込むようにオプションを設定しなおす
 options.inJustDecodeBounds = false;

 // 実際に画像を読み込み
 srcBmp = BitmapFactory.decodeFile(path, options);

 // 背景が透明のキャンバスに画像を合成する
 canvas.drawBitmap(srcBmp, mat, bmpPaint);

 // メモリ解放
 srcBmp.recycle();
 srcBmp = null;

 Log.i("#getBigPicture()", "scaleDensity=" + scaleDensity + " srcWidth="
   + srcWidth + " srcHeight=" + srcHeight + " targetWidthPx="
   + targetWidthPx + " targetHeightPx=" + targetHeightPx);
 return canvasBmp;
}

/**
 * @param sw ソース幅
 * @param sh ソース高さ
 * @param pw この幅にフィットさせる
 * @param ph この高さにフィットさせる
 * @param padding フィット時に考慮するパディング
 * @param enableMagnify 画像が小さい場合に拡大するか否か
 * @return フィットさせるためのスケール値
 */
private static final Matrix getMatrix(int sw, int sh, int pw, int ph,
  int padding, boolean enableMagnify) {
 float scale = getMaxScaleToParent(sw, sh, pw, ph, padding);
 if (!enableMagnify && scale > 1.0f) {
  scale = 1;
 }
 Log.i("#getMatrix()", "scale=" + scale);
 Matrix mat = new Matrix();
 mat.postScale(scale, scale);
 mat.postTranslate((pw - (int) (sw * scale)) / 2,
   (ph - (int) (sh * scale)) / 2);
 return mat;
}

private static final float getMaxScaleToParent(int sw, int sh, int pw,
  int ph, int padding) {
 float hScale = (float) (pw - padding) / (float) sw;
 float vScale = (float) (ph - padding) / (float) sh;
 return Math.min(hScale, vScale);
}

上記では、bigPictureの最大幅・高さのキャンバスを作り、そのキャンバスサイズに合わせて画像を縮小し、中央に配置する。

BigPictureStyleでは、450dpに合わせて、上下左右に隙間なく画像を表示するよう設計されている。しかしこの設計では、多くの場合に上下や左右に隙間ができても、常に画像全体が写るようにして、どんな画像か把握出来るようにしている。


上記メソッドを呼び出してBigPictureStyleを作るコードのスニペット。
NotificationCompat.BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle(
      notificationBuilder);
bigPictureStyle.bigPicture(getBigPicture(res, path));
bigPictureStyle.setBigContentTitle(contentTitle);
bigPictureStyle.setSummaryText(contentText);
notificationBuilder.setStyle(bigPictureStyle);


結果、bigPicture部分はこんなふうに表示される。

NotificationのBigPictureStyleで表示されるbigPictureの大きさはいくつなんだ

4.1のJelly Bean以降でサポートされるようになった、リッチなNotificationスタイル。

このうちのBigPictureStyleで表示できるbigPicture(画像部分)の大きさにあわせて、元画像全体をリサイズして表示したいと思い、サイズを調べてみた。


・高さは192dp

Android Developersのサイトでは、高さの上限は最大256dpと書いてある。
Through an improved notification builder, apps can create notifications that use a larger area, up to 256 dp in height.

同じ内容がGoogle I/O 2012のセッション(32分40秒あたり)でも触れられている。そのスライドでは、BigContentView全体で、256dpが最大の高さと示してある。



つまり、256dpはbigPictureの高さではなく、BigPictureStyleのNotification全体での高さ上限だということ。よって、bigPictureの高さはNotificationを広げた256dp(4U)の状態から、Notificationを広げていない64dp(1U)の状態を引いた値になる。

256dp(4U) - 64dp(1U) = 192dp(3U)


・幅は450dp

幅は、同じく上記のGoogle I/O 2012のセッション(36分30秒あたり)で、最大450dpだとある。



まとめるとこんな感じ



・pxだとどのくらい?

例えば、Nexus 7はtvdpiなので、ピクセル密度(density)は1.33125。

450dp x 1.33125 = 約599px
192dp x 1.33125 = 約255px

bigPicture部分には、幅599px、高さ255px程度の大きさの画像が表示される。