Android画像のぼかしとビットマップ関連
序章
タスク:ウィンドウポップアップの背景がぼやけている
実装原理:ウィンドウポップアップウィンドウの内容を表示する前に、まず携帯電話の画面のスクリーンショットを撮り、それをぼかします。ポップアップウィンドウが表示されたら、ポップのサイズに合わせてスクリーンショットを調整します。 -ポップアップウィンドウを背景として設定し、ポップアップウィンドウの背景をぼかす効果を実現します。
困難:
- 私たちのアプリケーションにはアクティビティが常駐しておらず、システムアプリケーションであるため、通常のアプリケーションのスクリーンショットとは異なります。
- リフレクションメソッドを使用してシステムAPIを呼び出してスクリーンショットを撮る場合、画面の高さを取得する際に落とし穴があり、特別な注意が必要です。
- ネイティブAPIを直接使用してぼかしを行う場合、半径を最大25に設定するとビジネス要件が満たされないため、画像を追加で処理する必要があります。
スクリーンショット
スクリーンショットを撮る方法はたくさんありますが、一般的に使用される方法は次のとおりです。
-
adbコマンドのスクリーンショット
スクリーンショットを撮るadbコマンドにはroot権限が必要であり、ioが含まれるため、時間がかかります。
private Bitmap getScreenByAdb(String localImgPath) { // adb命令获取屏幕截图,与实际屏幕尺寸相同,但是涉及IO所以耗时 Process process = null; try { process = Runtime.getRuntime().exec("screencap " + localImgPath); process.waitFor();//比较耗时,尤其屏幕色彩比较复杂时,耗时甚至会到3s } catch (Exception e) { e.printStackTrace(); } finally { if (process != null) { process.destroy(); } } FileInputStream fis = null; try { fis = new FileInputStream(localImgPath); } catch (FileNotFoundException e) { e.printStackTrace(); } Bitmap originalBgBitmap = BitmapFactory.decodeStream(fis); return originalBgBitmap; }
-
リフレクション呼び出しシステムAPIのスクリーンショット
この方法を使用してスクリーンショットを撮る場合は、システムアプリケーションである必要があります
private Bitmap getScreenBySysMethod() { DisplayMetrics dm = getContext().getResources().getDisplayMetrics(); Bitmap originalBgBitmap = null; try { Class<?> mClassType = Class.forName("android.view.SurfaceControl"); Method nativeScreenshotMethod = mClassType.getDeclaredMethod("screenshot", Rect.class, int.class, int.class, int.class); nativeScreenshotMethod.setAccessible(true); Bitmap sBitmap = (Bitmap) nativeScreenshotMethod.invoke(mClassType, new Rect(), dm.widthPixels, dm.heightPixels, Surface.ROTATION_0); originalBgBitmap = sBitmap.copy(Bitmap.Config.ARGB_8888, true); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return originalBgBitmap; }
この方法を使用してスクリーンショットを撮ったときに穴がありました。投げるのに長い時間がかかりました。一部のフルスクリーン携帯電話でdm.heightPixelsを直接使用して得られた画面の高さには、ステータスバーが含まれていません。ソースを参照してください。コードはdm.heightPixelsで、コメントは使用可能な表示サイズの絶対高さ(ピクセル単位)と記述されているため、ステータスバーを含めなくてもかまいません。現時点では、画面の高さを取得する別の方法があります。
/** * 在activity中获取屏幕的真实高度,由于在部分全面屏手机上, * 直接使用DisplayMetrics heightPixels获取屏幕高度没有包含状态栏或者虚拟按键 * 所以会比实际的小几十个像素 * @param context * @return */ public static int getScreenHeight(Activity context) { DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); context.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics); return displayMetrics.heightPixels; }
-
通常のアプリのスクリーンショット
ルートレイアウトを取得するにはgetWindow()。getDecorView()を呼び出す必要があるため、このメソッドは通常、アクティビティまたはフラグメントで呼び出されます。したがって、スクリーンショットを撮る必要がある場合、アクティビティアクティビティがない場合は、この方法を使用できません。
private Bitmap getScreenshotView() { View view = getWindow().getDecorView(); view.setDrawingCacheEnabled(true); // 设置缓存,可用于实时截图 Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); view.draw(canvas); view.setDrawingCacheEnabled(false); // 清空缓存,可用于实时截图 return bitmap; }
同時に、このメソッドはビュー全体でスクリーンショットを撮るため、メソッド内のビューオブジェクトがスクリーンショットが必要なビューに置き換えられている限り、特定のビューの部分的なスクリーンショットを撮ることもできます。
ビットマップブラー
GitHubには既製のファジーコントロールがあり、github.com / mmin18/Realなどの多くのシナリオに対応できます。残念ながら、それは私のニーズを満たしていません。ここで取得したビットマップをぼかすだけで、ネイティブAndroidを直接使用できます。コードは次のとおりです。
/**
*
* @param context 上下文
* @param src 原图
* @param radius 模糊半径,有效范围0-25
* @return
*/
private Bitmap blur(Context context, Bitmap src, float radius) {
RenderScript rs = RenderScript.create(context);
final Allocation input = Allocation.createFromBitmap(rs, src);
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setRadius(radius);
script.setInput(input);
script.forEach(output);
output.copyTo(src);
return src;
}
如果这个方法模糊度不够的话,还可以这样来做:先将bitmap缩小,再进行模糊,然后再放大,这样得到的模糊度一般是可以满足需求的。
缩小bitmap
BitmapTransformUtil.scaleBitmap(originalBgBitmap, 0.2f, 0.2f, true);
放大bitmap
BitmapTransformUtil.scaleBitmap(blurredBgBitmap, 5.0f, 5.0f, true);
其中scaleBitmap方法如下所示:
/**
* 根据指定的宽度比例值和高度比例值进行缩放
*
* @param srcBitmap
* @param scaleWidth
* @param scaleHeight
* @param recycleSrc 是否回收Bitmap
* @return
*/
public static Bitmap scaleBitmap(Bitmap srcBitmap, float scaleWidth, float scaleHeight, boolean recycleSrc) {
int width = srcBitmap.getWidth();
int height = srcBitmap.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap bitmap = Bitmap.createBitmap(srcBitmap, 0, 0, width, height, matrix, true);
if (bitmap != null) {
/**回收*/
if (recycleSrc && srcBitmap != null && !srcBitmap.equals(bitmap) && !srcBitmap.isRecycled()) {
GlideBitmapPool.putBitmap(srcBitmap);
}
return bitmap;
} else {
return srcBitmap;
}
}
Bitmap截取
拿到整个手机屏幕的截图后,需要借去部分作为背景,截取bitmap方法如下:
/**
* 裁剪一定高度保留上半部分
*
* @param srcBitmap 原图
* @param x 起始坐标x
* @param y 起始坐标y
* @param width 目标宽度
* @param height 目标高度
* @param recycleSrc 是否回收原图
* @return
*/
public static Bitmap cropBitmapTop(Bitmap srcBitmap, int x, int y, int width, int height, boolean recycleSrc) {
/**裁剪关键步骤*/
Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, x, y, width, height);
/**回收之前的Bitmap*/
if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) {
GlideBitmapPool.putBitmap(srcBitmap);
}
return cropBitmap;
}
Bitmap圆角设置
/**
* 设置圆角
*
* @param srcBitmap
* @param recycleSrc 是否回收原图
* @return
*/
public static Bitmap getRoundedCornerBitmap(Bitmap srcBitmap, float radius, boolean recycleSrc) {
Bitmap output = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Paint paint = new Paint();
Rect rect = new Rect(0, 0, srcBitmap.getWidth(), srcBitmap.getHeight());
RectF rectF = new RectF(rect);
paint.setAntiAlias(true);
Canvas canvas = new Canvas(output);
canvas.save();
canvas.drawARGB(0, 0, 0, 0);
canvas.drawRoundRect(rectF, radius, radius, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(srcBitmap, rect, rect, paint);
canvas.restore();
/**回收之前的Bitmap*/
if (recycleSrc && srcBitmap != null && !srcBitmap.equals(output) && !srcBitmap.isRecycled()) {
GlideBitmapPool.putBitmap(srcBitmap);
}
return output;
}
Bitmap保存
/**
* 保存bitmap到本地
* @param bitmap
* @param filePath
*/
private void saveBitmap(Bitmap bitmap, String filePath){
FileOutputStream fos = null;
try {
fos = new FileOutputStream(filePath);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
推荐一个bitmap复用池:github.com/amitshekhar…,毕竟bitmap是内存消耗大户,合理使用bitmap可以避免内存抖动和OOM。具体使用方法直接点进去看吧。
最后贴一个实现效果图吧