Android 彈窗毛玻璃背景實踐
需求: 點選FloatingActionButton彈出彈層,彈層底部有多個圖示可選,每一個圖示都是一個功能入口,背景采用毛玻璃模糊效果。
記錄一下這個需求的思考和實作過程。
查找Android原生API做法
在Android API裡面,有個FLAG_BLUR_BEHIND用于模糊背景的FLAG,嘗試一下使用它來做Dialog的背景模糊。
代碼如下:
final Dialog dialog = new Dialog(MainActivity.this, R.style.SquareEntranceDialogStyle);
Window window = dialog.getWindow();
if (window != null) {
window.setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
window.setGravity(Gravity.BOTTOM);
}
View layout = View.inflate(MainActivity.this, R.layout.square_posting_entrance_dialog, null);
dialog.setContentView(layout);
dialog.show();
<style name="SquareEntranceDialogStyle" parent="@android:style/Theme.Dialog">
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
最後出來的效果有點迷。在4.4的機器上,看不出任何模糊效果,而在7.0的機器上,dialog的彈窗背景出現了一片灰色,而遮罩層的效果沒有任何變化。
檢視了源碼,發現這個FLAG已經在4.1以後就廢棄了。
通過高斯模糊把處理後的圖檔作為彈層背景
換了一種思路,把遮罩模糊拆成了幾個步驟來實作
- 把dialog彈層鋪滿整個螢幕
- 通過應用内截圖的方式獲得彈層背後的圖檔bitmap
- 通過高斯模糊算法對截圖進行模糊處理
- 把模糊後的圖檔bitmap設定成dialog的background
把問題拆開以後,單獨實作每一個步驟就容易多了。
1.把dialog彈層鋪滿整個螢幕:
在拆分問題的一開始,我的思路是怎麼去更改dialog的遮罩層顔色,或者把遮罩層整體設定成圖檔,但嘗試了多種方法都無法實作,最後在stackoverflow查到了另一種思路,把dialog鋪滿螢幕,然後修改dialog的windowBackground來達到跟修改遮罩層相同的效果。
通過修改dialog style的windowIsFloating為false,可以使dialog鋪滿螢幕
<style name="SquareEntranceDialogStyle" parent="@android:style/Theme.Dialog">
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">false</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
把dialog的根布局改成match_parent,并加上一點白色背景色和透明度
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tool="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ebebeb"
android:alpha="0.8"
android:gravity="center_horizontal|bottom">
...
</LinearLayout>
2.通過應用内截圖的方式獲得彈層背後的圖檔bitmap
通過activity的window.getDecorView()擷取到視窗最頂層的view,然後開啟Cache機制使View可以儲存成Bitmap,在每次擷取View截圖前通過destroyDrawingCache()先清除上一次的舊緩存,然後通過buildDrawingCache()重新生成一張最新截圖,最後通過getDrawingCache()擷取截圖Bitmap
View activityView = getWindow().getDecorView();
activityView.setDrawingCacheEnabled(true);
activityView.destroyDrawingCache();
activityView.buildDrawingCache();
Bitmap bmp = activityView.getDrawingCache();
3.通過高斯模糊算法對截圖進行模糊處理
高斯模糊算法直接搜尋了一個封裝好了的工具類,通過RenderScript進行模糊處理
public class BlurBitmap {
/**
* 圖檔縮放比例
*/
private static final float BITMAP_SCALE = f;
/**
* 最大模糊度(在0.0到25.0之間)
*/
private static final float BLUR_RADIUS = f;
/**
* 模糊圖檔的具體方法
*
* @param context 上下文對象
* @param image 需要模糊的圖檔
* @return 模糊處理後的圖檔
*/
public static Bitmap blur(Context context, Bitmap image) {
// 計算圖檔縮小後的長寬
int width = Math.round(image.getWidth() * BITMAP_SCALE);
int height = Math.round(image.getHeight() * BITMAP_SCALE);
// 将縮小後的圖檔做為預渲染的圖檔。
Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false);
// 建立一張渲染後的輸出圖檔。
Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
// 建立RenderScript核心對象
RenderScript rs = RenderScript.create(context);
// 建立一個模糊效果的RenderScript的工具對象
ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
// 由于RenderScript并沒有使用VM來配置設定記憶體,是以需要使用Allocation類來建立和配置設定記憶體空間。
// 建立Allocation對象的時候其實記憶體是空的,需要使用copyTo()将資料填充進去。
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
// 設定渲染的模糊程度, 25f是最大模糊度
blurScript.setRadius(BLUR_RADIUS);
// 設定blurScript對象的輸入記憶體
blurScript.setInput(tmpIn);
// 将輸出資料儲存到輸出記憶體中
blurScript.forEach(tmpOut);
// 将資料填充到Allocation中
tmpOut.copyTo(outputBitmap);
return outputBitmap;
}
}
4.把模糊後的圖檔bitmap設定成dialog的background
最後擷取到模糊處理後的bitmap,設定到dialog的windowBackground中,即可做出彈層模糊背景的效果,完整代碼如下:
final Dialog dialog = new Dialog(MainActivity.this, R.style.SquareEntranceDialogStyle);
Window window = dialog.getWindow();
Bitmap blurBg = null;
if (window != null) {
long startMs = System.currentTimeMillis();
// 擷取截圖
View activityView = getWindow().getDecorView();
activityView.setDrawingCacheEnabled(true);
activityView.destroyDrawingCache();
activityView.buildDrawingCache();
Bitmap bmp = activityView.getDrawingCache();
Log.d(TAG, "getDrawingCache take away:" + (System.currentTimeMillis() - startMs) + "ms");
// 模糊處理并儲存
blurBg = BlurBitmap.blur(MainActivity.this, bmp);
Log.d(TAG, "blur take away:" + (System.currentTimeMillis() - startMs) + "ms");
// 設定成dialog的背景
window.setBackgroundDrawable(new BitmapDrawable(getResources(), blurBg));
bmp.recycle();
}
final Bitmap finalBlurBg = blurBg;
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
// 對話框取消時釋放背景圖bitmap
if (finalBlurBg != null && !finalBlurBg.isRecycled()) {
finalBlurBg.recycle();
}
}
});
View layout = View.inflate(MainActivity.this, R.layout.square_posting_entrance_dialog, null);
dialog.setContentView(layout);
dialog.show();
通過打Log檢測擷取截圖以及模糊處理所要消耗的時間,總處理時長大概在50~80ms,還算可以,沒有出現卡頓的情況。
最後補一張效果圖
參考資料:
How can I change default black dim background “color” (not the amount of dim) of Dialog?
Android高斯模糊(RenderScript)