模糊效果可以生動的表達(dá)內(nèi)容分層的含義。它允許用戶保留上下文,同時專注于當(dāng)前的特色內(nèi)容,即使在模糊表面下以視差方式變換或動態(tài)的更改。
在 iOS 中,我們可以通過首先構(gòu)建一個 UIVisualEffectView 來得到這種模糊:
UIVisualEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *visualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]
然后添加 visualEffectView 到視圖層次中,在視圖層次中它會動態(tài)的模糊它下面的東西。
當(dāng)事情并不像 Android 上那么簡單時,我們確實看到了模糊效果的很好的例子,如 Yahoo 天氣應(yīng)用程序。根據(jù) Nicholas Pomepuy’s blog post,然而,這里的模糊是通過緩存背景圖像的 pre-render 模糊版本得到的。
雖然這種方法非常有效,但是它并不完全適合我們的需要。500 像素的圖像通常是重點內(nèi)容,而不僅僅用于提供背景。那就意味著圖像可能會改變很多,變化很快,即使它們在模糊層下。Android 應(yīng)用程序中的 tour 就是一個很好的例子。在這里,隨著用戶滑動頁面,一排排的圖片向相反的方向轉(zhuǎn)變,并淡出,這使得為組合所需的模糊效果而恰當(dāng)?shù)墓芾矶鄠€預(yù)渲染圖像變得非常困難。
http://wiki.jikexueyuan.com/project/android-weekly/images/issue-145/2015-03-17-500px-android-tour-blurring.png" alt="Android的模糊視圖" />
Tour 需要的是一個模糊視圖,它能夠動態(tài)的實時的模糊它下面的視圖。我們最終到達(dá)的接口非常簡單,就如首次給定模糊視圖一個引用那樣:
blurringView.setBlurredView(blurredView);
然后每當(dāng)模糊視圖變化時——無論由于內(nèi)容變化(如顯示一個新的照片),視圖轉(zhuǎn)換,或動畫的一個步驟,模糊視圖都將無效:
blurringView.invalidate();
為了實現(xiàn)模糊視圖,我們可以生成 View 類的子類,并覆蓋 onDraw() 方法來呈現(xiàn)模糊效果:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Use the blurred view’s draw() method to draw on a private canvas.
mBlurredView.draw(mBlurringCanvas);
// Blur the bitmap backing the private canvas into mBlurredBitmap
blur();
// Draw mBlurredBitmap with transformations on the blurring view’s main canvas.
canvas.save();
canvas.translate(mBlurredView.getX() - getX(), mBlurredView.getY() - getY());
canvas.scale(DOWNSAMPLE_FACTOR, DOWNSAMPLE_FACTOR);
canvas.drawBitmap(mBlurredBitmap, 0, 0, null);
canvas.restore();
}
這里的關(guān)鍵是當(dāng)模糊視圖重繪時,它使用模糊視圖的 draw() 方法,它有一個參考,但是會畫到一個私有的,bitmap-backed 畫布上:
mBlurredView.draw(mBlurringCanvas);
(值得注意的是,這種調(diào)用另一種視圖的 draw() 方法的方式也適用于構(gòu)建一個 magnifier 或signature UI,其中 magnifier 的內(nèi)容或 signature 的面積是擴大的,而不是模糊的。)
按照在 Nicholas Pomepuy’s post 中討論的觀點,我們使用二次抽樣的組合以及 RenderScript 來快速處理。當(dāng)我們初始化模糊視圖的私有畫布 mBlurringCanvas 時,二次抽樣的設(shè)置就完成了:
int scaledWidth = mBlurredView.getWidth() / DOWNSAMPLE_FACTOR;
int scaledHeight = mBlurredView.getHeight() / DOWNSAMPLE_FACTOR;
mBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
mBlurringCanvas = new Canvas(mBitmapToBlur);
鑒于對 RenderScript 的設(shè)置和適當(dāng)?shù)爻跏蓟?,用?onDraw() 中的 blur() 方法非常簡單,如下所示:
mBlurInput.copyFrom(mBitmapToBlur);
mBlurScript.setInput(mBlurInput);
mBlurScript.forEach(mBlurOutput);
mBlurOutput.copyTo(mBlurredBitmap);
現(xiàn)在 mBlurredBitmap 已經(jīng)準(zhǔn)備好了,剩下的 onDraw() 方法負(fù)責(zé)使用適當(dāng)平移和縮放的方式,把它畫入模糊視圖的畫布中。
對于一個完整的實現(xiàn),我們需要注意幾個技術(shù)點。首先,我們發(fā)現(xiàn) 8 倍的 downsampling 縮放和 15 的模糊半徑對我們更有利。適合于你的參數(shù)可能會不同。
第二,我們在模糊位圖的邊緣發(fā)現(xiàn)了一些 RenderScript 工件。為了辨識這些,我們將 scaled 的寬度和高度提高到最近的 4 的倍數(shù):
// The rounding-off here is for suppressing RenderScript artifacts at the edge.
scaledWidth = scaledWidth - (scaledWidth % 4) + 4;
scaledHeight = scaledHeight - (scaledHeight % 4) + 4;
第三,為了進(jìn)一步確保良好的性能,我們創(chuàng)建了兩個位圖 mBitmapToBlur,支持私有畫布 mBlurringCanvas,和 mBlurredBitmap 需求,并且只有當(dāng)模糊視圖的大小發(fā)生變化時,才需要重建它們。同樣地,只有在模糊視圖的大小發(fā)生變化時,我們才創(chuàng)建 RenderScript 的 Allocation 對象 mBlurInput 和 mBlurOutput。
第四,我們也用 PorterDuff.Mode.OVERLAY 在模糊圖像的頂部畫一層均勻的,半透明的白色來突出我們的設(shè)計需求。
最后,因為 RenderScript 只在 API level 17 及更高的版本上可用,我們需要在 Android 舊版本上完全降低。不幸的是,正如 Nicholas Pomepuy’s post 中提到的,Java中的位圖模糊解決方案,雖然適合于預(yù)呈現(xiàn)一個緩存副本,但是實時渲染不夠快。我們作出的決定是簡單地使用一個具有高透明度的半透明視圖作為候選。(2015 年 3 月 23 日更新:通過使用 RenderScript 支持庫,可以更低水平的 API 下使用我們的解決方案。以下提到的庫和樣例已經(jīng)更新,以反映這一點。感謝 GitHub 用戶 panzerdev 指出這一點,并發(fā)送了請求。)
我們喜歡這個視圖繪圖方法因為它是實時模糊的,容易使用,它允許模糊視圖內(nèi)容的不可知性,它還允許模糊和模糊視圖之間關(guān)系的靈活性,最重要的是,它適合我們的需要。
然而,這種方法確實期望模糊視圖參與適當(dāng)?shù)淖鴺?biāo)變換的模糊視圖的下落。相應(yīng)地,在模糊視圖不能是模糊視圖的子視圖,否則你會從相互嵌套調(diào)用得到一個堆棧溢出。這種限制下一個簡單的原則是確保這個模糊視圖是 z 值在它前面的模糊視圖的同層。
我們已經(jīng)注意到的另一個限制是矢量圖和文本,如果我們使用默認(rèn)的位圖downsampling,它們不能很好的起作用。
為了看到我們的解決方案,你可以查看 Android 應(yīng)用程序中的 tour。我們也在 GitHub 中建立一個小型的開源庫,以及詳細(xì)演示,展示了內(nèi)容變化,動畫以及視圖轉(zhuǎn)換的使用方法。
http://wiki.jikexueyuan.com/project/android-weekly/images/issue-145/blurdemo.gif" alt="Android的模糊視圖" />