Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
SS設(shè)置背景模糊
在做一些頁面的時(shí)候,為了讓頁面更好看,我們常常需要設(shè)置一些背景圖片,但是,當(dāng)背景圖片太過花哨的時(shí)候,又會(huì)影響我們的主體內(nèi)容,所以我們就需要用到filter屬性來設(shè)置他的模糊值。
html代碼如下
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> </head> <body> <div class="cover"> <h1>我是需要突出顯示的內(nèi)容 </div> </body> </html>
但是如果直接在背景圖片上使用的話,
.cover{ width:600px; height:300px; position:relative; text-align:center; line-height:300px; color:white; background:transparent url(http://91jean.oss-cn-hangzhou.aliyuncs.com/18-8-31/16567303.jpg) center center no-repeat; filter:blur(5px); background-size:cover; }
可能會(huì)造成下面的這種情況。
我們會(huì)發(fā)現(xiàn)背景和主體內(nèi)容都變糊了。
解決辦法:給背景圖片增加一個(gè)偽元素,將背景圖片和filter屬性設(shè)置在偽元素上,具體代碼如下
.cover{ width:600px; height:300px; position:relative; text-align:center; line-height:300px; color:white; } .cover::before{ content:''; position:absolute; top:0; left:0; width:600px; height:300px; background:transparent url(http://91jean.oss-cn-hangzhou.aliyuncs.com/18-8-31/16567303.jpg) center center no-repeat; filter:blur(5px); z-index:-1; background-size:cover; }
復(fù)制代碼
言
在一名 Android 程序猿的職業(yè)生涯中,大概率會(huì)與設(shè)計(jì)獅有過這樣的討論:
:“我要這個(gè)毛玻璃效果”
:“這個(gè)效果實(shí)現(xiàn)不了”
(掏出iPhone):“你看人家蘋果都有,你怎么做不了?”
:“iOS 可以,Android 真的做不了,童叟無欺”
這個(gè)讓設(shè)計(jì)師心心念念的視覺效果,就是背景模糊。
iOS 8 加入的 UIVisualEffectView、CSS 中的backdrop-filter 以及 Flutter 中的 BackdropFilter 類,都可以實(shí)現(xiàn)這個(gè)效果,只有 Android 一直沒有支持該能力。
在 Android 領(lǐng)域內(nèi),背景模糊效果有兩個(gè)不同場(chǎng)景,需要做一下區(qū)分:
Window 級(jí)的背景模糊,多年以來各手機(jī)廠商都有自己的實(shí)現(xiàn)方案。而 Android 12 里,AOSP 對(duì)SurfaceFlinger 進(jìn)行了重構(gòu),GPU 合成的部分也使用 Skia 進(jìn)行渲染,同時(shí)對(duì)跨 Window 的背景模糊做了官方的支持[1],并 public 了相關(guān)的 API。
Android 12 支持的Window背景模糊
(模糊內(nèi)容屬于背后的窗口)
而本文要討論的是后者——View 級(jí)背景模糊的實(shí)現(xiàn)方式。
iOS 上的 View 級(jí)背景模糊效果
(模糊內(nèi)容屬于同一窗口內(nèi)背后的控件)
筆者整理了現(xiàn)在開源庫中的兩類解決方案,外加酷派團(tuán)隊(duì)調(diào)研出的兩種方案,共四種寫法,供大家參考。
開源方案
如果要從應(yīng)用側(cè)實(shí)現(xiàn)這個(gè)效果,最重要的一個(gè)步驟是獲取模糊控件背景的內(nèi)容,目前 Github 上的相關(guān)框架,大概分為兩個(gè)方案:
方案一:找到背景控件、調(diào)用draw()
代表框架:
500px/500px-android-blur、Dimezis/BlurView、mmin18/RealtimeBlurView,這三個(gè)框架的??數(shù)量均為 2.7k 左右,接受度比較高。
創(chuàng)建一個(gè)鏈接到 Bitmap 的離屏Canvas,在模糊控件繪制之前,將下層布局手動(dòng)繪制到這個(gè) Canvas 里,這樣在 Bitmap 里就拿到了控件背景內(nèi)容。
其中:500px-android-blur 的做法是手動(dòng)指定背景的布局,在模糊控件 onDraw 的時(shí)候,對(duì)指定的下層布局進(jìn)行繪制。這種做法實(shí)現(xiàn)起來比較簡(jiǎn)單,但每次都需要手動(dòng)指定下層布局,而且模糊控件不能包含在該布局里,缺少靈活性。
// 手動(dòng)指定下層布局
blurringView.setBlurredView(blurredView);
而 Dimezis/BlurView、mmin18/RealtimeBlurView 的解法則更加靈活,下層布局不用特別指定,直接使用rootView(一般為DecorView)。在模糊控件onPreDraw的時(shí)候,將rootView繪制到離屏Canvas。
但 rootView 并不是下層布局,因?yàn)槟:丶舶趦?nèi)。這兩個(gè)框架使用比較取巧的辦法解決了這個(gè)問題:在draw(Canvas)方法里,判斷如果是離屏 Canvas 在繪制,則跳過自身繪制。
獲取背景內(nèi)容后,剩下的步驟則是對(duì) Bitmap 進(jìn)行模糊處理并繪制,由于比較簡(jiǎn)單就不贅述了。
方案效果(以500px-android-blur為例)
方案缺點(diǎn)主要是額外的性能開銷。
方案二:Canvas GL Functor
方案效果
這個(gè)框架使用起來非常簡(jiǎn)單,不需要做額外的設(shè)置,只需要給View設(shè)置一個(gè)background即可。
final BlurDrawable blurDrawable=new BlurDrawable();
view.setBackgroundDrawable(blurDrawable);
調(diào)查了源碼,它也沒有把下層布局再次繪制,那它是如何獲取到背景內(nèi)容的?
秘密在于這個(gè)隱藏方法:RecordingCanvas.callDrawGLFunction2()
/**
* Records the functor specified with the drawGLFunction function pointer. This is
* functionality used by webview for calling into their renderer from our display lists.
*
* @param drawGLFunction A native function pointer
*
* @hide
* @deprecated Use {@link #drawWebViewFunctor(int)}
*/
@Deprecated
public void callDrawGLFunction2(long drawGLFunction) {
nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunction, null);
}
這個(gè)隱藏的方法看起來比較陌生,因?yàn)樗皇窃O(shè)計(jì)給 App 使用的。它的作用是將外部的 OpenGL 方法鏈接到 Canvas 的繪制流程里,目前官方的使用場(chǎng)景是 Android 的 WebView。
WebView 使用自己的 OpenGL 方法對(duì)網(wǎng)頁進(jìn)行渲染,再調(diào)用這個(gè)方法,將結(jié)果鏈接到你的 App Window 里。
這剛好能解釋,為什么 WebView 使用獨(dú)立的渲染機(jī)制,但不需要使用 SurfaceView 的獨(dú)立 layer 也能顯示到屏幕上。
經(jīng)過一番調(diào)查發(fā)現(xiàn),參數(shù)drawGLFunction,是 Android Native 層的一個(gè)通用函數(shù)指針,結(jié)構(gòu)如下:
// File: system/core/libutils/include/utils/Functor.h
class Functor {
public:
Functor() {}
virtual ~Functor() {}
virtual status_t operator()(int /*what*/, void* /*data*/) { return OK; }
};
在 UI 線程調(diào)用callDrawGLFunction2()方法后,只是設(shè)置了函數(shù)指針,并沒有起到繪制效果。真正的繪制邏輯,發(fā)生在 RenderThread 里:
// File: frameworks/base/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
// 省略部分代碼
……
GLuint fboID=0;
SkISize fboSize;
GetFboDetails(canvas, &fboID, &fboSize);
// 省略部分代碼:初始化GLContext、判斷離屏Layer等
……
DrawGlInfo info;
info.clipLeft=clipBounds.fLeft;
info.clipTop=clipBounds.fTop;
info.clipRight=clipBounds.fRight;
info.clipBottom=clipBounds.fBottom;
info.isLayer=fboID !=0;
info.width=fboSize.width();
info.height=fboSize.height();
mat4.getColMajor(&info.transform[0]);
info.color_space_ptr=canvas->imageInfo().colorSpace();
// 省略部分代碼:綁定FBO、設(shè)置GL環(huán)境
……
if (mAnyFunctor.index()==0) {
std::get<0>(mAnyFunctor).handle->drawGl(info);
} else {
// 這里會(huì)調(diào)用到函數(shù)指針Functor
(*(std::get<1>(mAnyFunctor).functor))(DrawGlInfo::kModeDraw, &info);
}
// 省略部分代碼
……
}
看到這里,為防止有讀者不了解 hwui,補(bǔ)充一些前提知識(shí):
我們都知道 Android 的 View 系統(tǒng),在底層是使用 Skia 圖形庫進(jìn)行渲染,而連接 View 與 Skia 的組件便是 libhwui。App 界面的繪制指令,最終都通過 hwui 庫,在 RenderThread 中得以執(zhí)行。
Skia 的SkDrawable結(jié)構(gòu),與 Android 的Drawable類似,都是在onDraw(canvas)方法中執(zhí)行繪制指令,實(shí)際上后者也是對(duì)前者的一個(gè)效仿設(shè)計(jì)。
在 hwui 庫中,一共有這幾種 SkDrawable 類型:
而方案二,正是使用了 GLFunctorDrawable。
回到這個(gè)庫中,上面的GLFunctorDrawable,會(huì)調(diào)用上層設(shè)置過來的 functor方法,并將此時(shí)的DrawGlInfo傳遞過去。
我們來看看這個(gè)庫的 Functor 真正做了些什么:
經(jīng)過 jni 的多次中轉(zhuǎn),F(xiàn)unctor 最終調(diào)用到了上面的 Java 層邏輯。與一般的 Java 層邏輯不同,圖中的代碼實(shí)際上運(yùn)行在 RenderThread 中,而且擁有 GLFunctorDrawable 已經(jīng)設(shè)置好的 OpenGL 上下文。
這里的邏輯大概分為三步:獲取當(dāng)前屏幕內(nèi)容、進(jìn)行 X/Y 軸兩次模糊運(yùn)算、放大并疊加顏色。
最關(guān)鍵的代碼就是這行glCopyTexSubImage2D,它可以將當(dāng)前屏幕已繪制內(nèi)容進(jìn)行區(qū)域拷貝[3]。由于這個(gè)代碼的執(zhí)行在背景內(nèi)容繪制和控件內(nèi)容繪制之間,這樣便獲取到了控件的背景內(nèi)容。
這個(gè)方案使用GLFunctorDrawable的機(jī)制,將自己的 OpenGL 指令嵌入到 RenderThread 每一幀的繪制中,獲取背景內(nèi)容,做模糊并上屏。
當(dāng)然,這個(gè)方案的缺點(diǎn)也十分明顯,導(dǎo)致它無法在 app 里商用。
callDrawGLFunctor2 是一個(gè) hide 方法,其在不同 Android 版本都有不同實(shí)現(xiàn),框架本身做了多平臺(tái)兼容。
但在 Google 對(duì) hide 方法的態(tài)度及采取的措施面前,這個(gè)做法顯得不可持續(xù)。
首先,Android 11 開始采取了更嚴(yán)格的反射限制,框架使用者需要額外去處理限制突破邏輯。
其次,callDrawGLFunctor2(long functor)是一個(gè)廢棄方法,在 Android 12 開始被移除。
在 Android 12 中,被換成 drawWebViewFunctor(int functor),不僅 functor 換了結(jié)構(gòu),還必須同時(shí)支持 OpenGL 和 Vulkan 兩種實(shí)現(xiàn)。
這個(gè)方案的兼容成本已經(jīng)非常高了,框架作者也沒有繼續(xù)進(jìn)行維護(hù),目前該框架在 Android 12 上無法使用。
在 hwui 的 pipeline 里,如果這塊內(nèi)容被繪制到了一塊離屏 buffer 再上屏,那么這里的 GLFunctor 便無法獲取到控件背后的內(nèi)容。
這是因?yàn)椋?hwui 每一幀繪制開始之前,會(huì)先把離屏的 Layer 先渲染完成得到結(jié)果,再把這些結(jié)果當(dāng)做圖像資源,在這一幀參與繪制。
需要離屏 buffer 的場(chǎng)景有這幾種:設(shè)置小于 1 的 alpha、需要 clip 的 Functor、有拉伸或者RenderEffect 效果(Android 12 添加)等,比較常見。
// File: frameworks/base/libs/hwui/RenderProperties.h
bool promotedToLayer() const {
return mLayerProperties.mType==LayerType::None && fitsOnLayer() &&
// 是functor且有clip、animation、translation等情況。
(mComputedFields.mNeedLayerForFunctors ||
// 設(shè)置了RenderEffect(Android 12新增)
mLayerProperties.mImageFilter !=nullptr ||
// 當(dāng)前有拉伸效果(Android 12新增)
mLayerProperties.getStretchEffect().requiresLayer() ||
// 當(dāng)前View設(shè)置了alpha,且hasOverlappingRendering為true
(!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 &&
mPrimitiveFields.mHasOverlappingRendering));
}
所以當(dāng) GLFunctor 被離屏渲染時(shí),它會(huì)提前執(zhí)行,此時(shí)便無法獲取到它背后的內(nèi)容。也就是說這種方案,無法對(duì)模糊控件設(shè)置 alpha 或者切圓角,這點(diǎn)也在框架的 issues 里得到了印證:
自研方案
方案三:擴(kuò)展 Canvas.saveLayer()
筆者發(fā)現(xiàn),F(xiàn)lutter 中的 BackdropFilter 類也能實(shí)現(xiàn)效果,其也使用 Skia 作為渲染引擎。
BackdropFilter這個(gè) Dart 層的 Widget,經(jīng)過 SceneBuilder 的中轉(zhuǎn),最終映射到 native 層的BackdropFilterLayer類:
它并沒有特殊的繪制邏輯,只是在繪制 children 內(nèi)容之前,調(diào)用了一個(gè)saveLayer操作。難道 Flutter僅僅通過saveLayer,就實(shí)現(xiàn)了背景模糊?
Android中也有Canvas.saveLayer()這個(gè)API,其作用是創(chuàng)建一個(gè)新的Layer,后續(xù)的繪制均發(fā)生在這個(gè)新的Layer里,繪制完成后將結(jié)果再一起上屏。所以這個(gè)方法開銷比較大,非必要不建議使用。
更關(guān)鍵的是,它并不支持背景模糊功能。
看來紅框中的這一行代碼是關(guān)鍵。調(diào)查SaveLayerRec發(fā)現(xiàn),它是 skia 引擎中的結(jié)構(gòu)體,大致結(jié)構(gòu)如下:
enum SaveLayerFlagsSet {
kInitWithPrevious_SaveLayerFlag=1 << 2, //!< initializes with previous contents
};
struct SaveLayerRec {
SaveLayerRec(const SkRect* bounds, const SkPaint* paint, SaveLayerFlags saveLayerFlags=0)
: fBounds(bounds)
, fPaint(paint)
, fSaveLayerFlags(saveLayerFlags)
{}
SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop, SaveLayerFlags saveLayerFlags)
: fBounds(bounds)
, fPaint(paint)
, fBackdrop(backdrop)
, fSaveLayerFlags(saveLayerFlags)
{}
/** hints at layer size limit */
const SkRect* fBounds=nullptr;
/** modifies overlay */
const SkPaint* fPaint=nullptr;
/**
* If not null, this triggers the same initialization behavior as setting
* kInitWithPrevious_SaveLayerFlag on fSaveLayerFlags: the current layer is copied into
* the new layer, rather than initializing the new layer with transparent-black.
* This is then filtered by fBackdrop (respecting the current clip).
*/
const SkImageFilter* fBackdrop=nullptr;
/** preserves LCD text, creates with prior layer contents */
SaveLayerFlags fSaveLayerFlags=0;
};
這里的SkImageFilter是 Skia 中可以實(shí)現(xiàn)圖形效果的工具類,常見的 filter 有:Blur、ColorFilter、Matrix、XferMode 等等。
Skia 在很早就支持了這個(gè)能力,在 Android 12 中,谷歌在上層封裝了RenderEffect類,第一次將其開放給上層調(diào)用[4]。
看下真正執(zhí)行模糊運(yùn)算的地方:
void SkCanvas::internalSaveLayer(const SaveLayerRec& rec, SaveLayerStrategy strategy) {
// 省略代碼
……
// If we have a backdrop filter, then we must apply it to the entire layer (clip-bounds)
// regardless of any hint-rect from the caller. skbug.com/8783
if (rec.fBackdrop) {
bounds=nullptr;
}
// 兩種情況下會(huì)繪制背景內(nèi)容
bool initBackdrop=(rec.fSaveLayerFlags & kInitWithPrevious_SaveLayerFlag) || rec.fBackdrop;
// 省略代碼
……
if (initBackdrop) {
DrawDeviceWithFilter(priorDevice, rec.fBackdrop, newDevice.get(), { ir.fLeft, ir.fTop }, fMCRec->fMatrix.asM33());
}
// 省略代碼
……
}
void SkCanvas::DrawDeviceWithFilter(SkBaseDevice* src, const SkImageFilter* filter, SkBaseDevice* dst, const SkIPoint& dstOrigin, const SkMatrix& ctm) {
// 省略代碼
……
// 截取當(dāng)前已經(jīng)繪制的內(nèi)容
auto special=src->snapSpecial(backdropBounds);
if (!special) {
return;
}
// 省略代碼
…
SkIPoint offset;
// 使用指定的filter進(jìn)行處理
special=as_IFB(filter)->filterImage(ctx).imageAndOffset(&offset);
if (special) {
offset +=layerInputBounds.topLeft();
SkMatrix dstCTM=toRoot;
dstCTM.postTranslate(-dstOrigin.x(), -dstOrigin.y());
dstCTM.preTranslate(offset.fX, offset.fY);
// 將處理結(jié)果進(jìn)行繪制
dst->drawSpecial(special.get(), dstCTM, sampling, p);
}
// 省略代碼
……
}
結(jié)合注釋與代碼得知,有兩個(gè)組合可以實(shí)現(xiàn)背景模糊效果:
區(qū)別在于,前者會(huì)將整個(gè) Canvas 的內(nèi)容都進(jìn)行處理,然后 clip 到相應(yīng)區(qū)域;而后者只會(huì)截取指定區(qū)域的內(nèi)容,另外也不會(huì)立即處理,而是選擇在新 layer 上屏的時(shí)刻統(tǒng)一處理。
Flutter 使用的是第一個(gè)組合。筆者兩個(gè)方案都進(jìn)行了嘗試,本文與Flutter保持一致,討論第一種組合。
看完 Flutter,我們?cè)賮砜纯?Android 里的saveLayer邏輯,經(jīng)過一些中轉(zhuǎn),它最終調(diào)用到了這里
// File: frameworks/base/libs/hwui/SkiaCanvas.cpp
int SkiaCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, SaveFlags::Flags flags) {
const SkRect bounds=SkRect::MakeLTRB(left, top, right, bottom);
// 這里只用到了 bounds, paint, layerFlags 三個(gè)參數(shù)
const SkCanvas::SaveLayerRec rec(&bounds, paint, layerFlags(flags));
return mCanvas->saveLayer(rec);
}
可以看到,Android 直接忽略了后面兩個(gè)參數(shù),并沒有提供任何暴露途徑。
那我們的思路也就很簡(jiǎn)單了:
<?xml version="1.0" encoding="utf-8"?>
<coolx.graphics.drawable.BackdropBlurDrawable
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:blurRadius="30dp"
app:saturation="1.8"
app:fallbackColor="#AAFFFFFF">
<shape
android:shape="rectangle">
<solid android:color="#BFEFEFEF"/>
</shape>
</coolx.graphics.drawable.BackdropBlurDrawable>
實(shí)機(jī)效果演示
這個(gè)方案我提交到了AOSP[5],谷歌工程師給了兩個(gè)反饋:
第一條反饋與實(shí)際表現(xiàn)不符合,關(guān)于性能是否符合要求,需要進(jìn)一步的調(diào)查和實(shí)驗(yàn)。
但第二條確實(shí)如此,所以還需要繼續(xù)找尋新的解法。
方案四:修改libhwui,增加模糊計(jì)算
View 的 alpha 發(fā)生改變,其實(shí)是設(shè)置的RenderNode.setAlpha()方法。
方案二與方案三,由于都使用了Canvas的接口,所以無論是重寫View.onDraw()方法,還是封裝 Drawable,這個(gè)調(diào)用指令都在該View的RenderNode內(nèi)部。這樣當(dāng) alpha 變化時(shí),就無法獲取到背景內(nèi)容。
除了 alpha 外,這次也打算將所有的邊界場(chǎng)景一次考慮清楚:View 的 transform、動(dòng)畫、clip 等。目前能想到的方案,是讓背景模糊邏輯脫離RenderNode。
參照前文,RenderNode的真正繪制,是在RenderNodeDrawable中,我們可以新定義一個(gè)BackdropFilterDrawable類型,與其平級(jí)。
將BackdropFilterDrawable的繪制順序,提前到RenderNodeDrawable之前即可。
BackdropFilterDrawable類的關(guān)鍵邏輯如下:
void BackdropFilterDrawable::onDraw(SkCanvas* canvas) {
// 對(duì)后面內(nèi)容進(jìn)行截圖(并不會(huì)創(chuàng)建新的buffer),此截圖為Canvas完整截圖。
auto backdropImage=canvas->getSurface()->makeImageSnapshot();
// 從target RenderNode那里,同步properties,無論它是否在做動(dòng)畫、縮放、是否有clip等,都進(jìn)行同步。計(jì)算結(jié)果保存到 mImageSubset 里,這是我們上層RenderNode真正的可見區(qū)域。
if (!prepareToDraw(canvas, properties, backdropImage->width(), backdropImage->height())) {
// 當(dāng)返回false的時(shí)候,說明不可見,則我們也跳過繪制。
return;
}
auto imageSubset=mImageSubset.roundOut();
// 將截圖里的上層區(qū)域進(jìn)行filter處理。
backdropImage= backdropImage->makeWithFilter(canvas->recordingContext(), backdropFilter, imageSubset,
imageSubset, &mOutSubset, &mOutOffset);
// 將filter結(jié)果進(jìn)行繪制。
canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds,
SkSamplingOptions(SkFilterMode::kLinear), &mPaint,
SkCanvas::kStrict_SrcRectConstraint);
}
,時(shí)長(zhǎng)00:06
與方案三相同,優(yōu)勢(shì)在于性能和效果。
這個(gè)方案我也提交到了AOSP[6],目前狀態(tài)為待 Review。感興趣的可以編譯看下效果。
方案對(duì)比
我們?cè)诳崤蒀OOL 20s 5G上,將四種方案進(jìn)行橫向?qū)Ρ取?/span>
這臺(tái)機(jī)器配置為天璣700、6GB內(nèi)存、128GB存儲(chǔ)、1080p 90Hz的屏幕。
,時(shí)長(zhǎng)00:02
使用perfetto抓取的trace,來衡量和計(jì)算每種方案在Choreographer.doFrame()和RenderThread分別消耗的時(shí)間。
在perfetto里可以直觀地看到平均耗時(shí)
抓取無模糊效果的耗時(shí),作為基準(zhǔn)指標(biāo)。分別為:doFrame 1.588ms, RenderThread: 3.485ms。
最終對(duì)比結(jié)果如下:
方案一 | 方案二 | 方案三 | 方案四 | |
實(shí)現(xiàn)復(fù)雜度 | 高 | 高 | 低 | 中 |
位移 | ?? | ?? | ?? | ?? |
縮放 | ? 內(nèi)容錯(cuò)誤 | ?? | ?? | ?? |
clip圓角 | ?? | ? | ?? | ?? |
alpha | ?? | ? | ? | ?? (parent alpha仍然不支持) |
視覺效果 | ? 僅支持模糊 | ? 僅支持模糊 | ?? 支持更多效果擴(kuò)展(如saturation) | ?? 支持更多效果擴(kuò)展(如saturation) |
兼容性 | ?? | ? Android 12不兼容 | 修改源碼 | 修改源碼 |
doFrame 性能開銷 | +2.934ms (4.552ms) | -0.167ms (1.421ms) | -0.071ms (1.517ms) | -0.047ms (1.547ms) |
RenderThread 開銷 | +0.587ms (4.072ms) | +1.678ms (5.163ms) | +1.771ms (5.256ms) | +1.579ms (5.064ms) |
方案一綜合開銷最高,后面三個(gè)方案的doFrame耗時(shí),與基準(zhǔn)耗時(shí)的差異在誤差范圍內(nèi),幾乎沒有引入額外的計(jì)算。
綜合來看,方案四各方面表現(xiàn)都很優(yōu)秀,酷派在自研的COOLOS里,已經(jīng)有多處采用了它。
后記
經(jīng)過這樣一番調(diào)研,筆者的有很多感悟和提升,其中感觸最深的是:如果對(duì)一塊領(lǐng)域感興趣,但網(wǎng)絡(luò)和社區(qū)沒有更好解法時(shí),就自己讀源碼吧。
在這個(gè)能力的調(diào)研過程中,有非常多有意思的技術(shù)問題,每一項(xiàng)都值得深入去探討和研究。
比如:
由于篇幅限制,本文不再展開,有機(jī)會(huì)可以開些續(xù)文,詳細(xì)講講。
感興趣的讀者,歡迎評(píng)論區(qū)跟我們一起討論!
作者:每天都吃麥當(dāng)勞
來源-微信公眾號(hào):酷派技術(shù)團(tuán)隊(duì)
出處:https://mp.weixin.qq.com/s/GJU2JxrjqRpyuJkrHJNC1w
砂玻璃效果已經(jīng)在互聯(lián)網(wǎng)上流行了很多年,Mac OS以其磨砂玻璃效果而聞名,Windows 10也通過其他一些燈光,深度,運(yùn)動(dòng),材質(zhì),比例尺實(shí)現(xiàn)了磨砂玻璃的效果
在CSS中使用磨砂玻璃效果時(shí),我們中的一些人知道該怎么做,而其他人仍會(huì)在百度搜索:
“ css光澤效果”
“ css毛玻璃”
“透明模糊背景css”
“毛玻璃效果photoshop”
“僅cs模糊背景”
“ css玻璃窗格”
“ css背景濾鏡”
“ css模糊覆蓋物”
“ css div后面的模糊背景”
今天,我將展示僅CSS的一種方法,教你可以使用該方法在CSS中進(jìn)行磨砂玻璃效果。
為簡(jiǎn)單起見,我將向你展示如何在空的div上制作磨砂玻璃效果。因此,HTML中所需的只是一個(gè)空的div。
<div> </div>
*{
margin: 0;
padding: 0;
}
我們需要我們的背景占據(jù)頁面的整個(gè)寬度和高度,并且我們不想重復(fù)我們的背景,我們也希望我們的背景是固定的。我們希望背景是固定的,因?yàn)槲覀儾幌M院笤诶^承背景時(shí)將整個(gè)背景顯示在div中。
body{
background-image: url(http://bit.ly/2gPLxZ4); //add "" if you want
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
}
現(xiàn)在,我們將使用背景繼承為div設(shè)置一些寬度和高度。我們還需要確定絕對(duì)位置,以確保疊加層不會(huì)占用網(wǎng)頁的整個(gè)寬度和高度
div{
background: inherit;
width: 250px;
height: 350px;
position: absolute;
}
現(xiàn)在,我們知道在固定了背景附件的情況下,我們只能看到div后面的div內(nèi)部的背景圖像區(qū)域,而這正是我們希望毛玻璃效果起作用的地方
我們需要content: “”確保之前的偽類能夠正常工作。我們還從其父級(jí)繼承了背景,并且我們使用絕對(duì)位置將其在其父元素DIV中對(duì)齊。我們正在使用盒子陰影添加白色透明疊加層,并且正在使用模糊來模糊該疊加層。
div:before{
content: “ ”;
background: inherit;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
box-shadow: inset 0 0 0 3000px rgba(255,255,255,0.3);
filter: blur(10px);
}
現(xiàn)在,我們需要修復(fù)div的未模糊邊緣,為此,我們需要增加疊加層的尺寸,使其比其父尺寸稍高一些,然后將其上下位置設(shè)為負(fù)(-25)。我們還需要給它的父對(duì)象提供隱藏的溢出,以確保父DIV之外的任何覆蓋都不會(huì)顯示并被隱藏。
div{
background: inherit;
width: 250px;
height: 350px;
position: absolute;
overflow: hidden; //adding overflow hidden
}
div:before{
content: ‘’;
width: 300px;
height: 400px;
background: inherit;
position: absolute;
left: -25px; //giving minus -25px left position
right: 0;
top: -25px; //giving minus -25px top position
bottom: 0;
box-shadow: inset 0 0 0 200px rgba(255,255,255,0.3);
filter: blur(10px);
}
到這里,我們就實(shí)現(xiàn)了CSS的磨砂玻璃效果
你還可以使用CSS屬性“backdrop-filter: blur(20px)”來實(shí)現(xiàn)此效果,該屬性在工作方面要容易得多,并且是更好的選擇,但兼容性還不是很強(qiáng)。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。